block是什么
block底层就是一个OC对象, 封装函数调用及调用环境的OC对象, 内部有isa指针
源代码
^{
int a = 10;
a = 20;
};
oc代码转换为c++代码后大体是这样的
// block实现, 有一个isa指针
struct __block_impl {
// isa指针表明block本质上是一个oc对象
void *isa;
int Flags;
int Reserved;
// 函数地址
void *FuncPtr;
};
// block实现
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 放block块中的代码实现
void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = 10;
}
// 放block基础信息的结构体
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
// Block实现
&__main_block_impl_0(
// block块内的代码实现放到了__main_block_func_0这个函数中
__main_block_func_0,
// __main_block_desc_0类型, 存放一些所占内存大小的信息
&__main_block_desc_0_DATA)
);
结构如图
block的变量捕获机制
auto修饰的局部变量
捕获到block内部, 值传递, 不可修改捕获的这个变量的值
源代码
// auto默认省略不写
int a = 10;
void (^block)(void) = ^{
NSLog(@"%d", a);
};
block();
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void (^block)(void) = ^{
NSLog(@"%d", a);
};
block();
}
return 0;
}
c++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
从第4行这里可以看出block内部会增加一个跟外部同名的变量, 并把block外部的值赋值给这个变量, 所以会给我们一假象在block内部访问外部的变量
static修饰的局部变量
捕获到block内部, 址传递, 可通过这个地址, 修改对应的内存数据
源代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int a = 10;
void (^block)(void) = ^{
NSLog(@"%d", a);
};
block();
}
return 0;
}
c++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
static int a = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
第四行的int *a和第18行的&a可以看出, block在捕获static修饰的变量时, 通过&a捕获外部变量a的地址值赋值给自己的int *a这个内部变量
全局变量
不捕获, 直接访问
源代码
int a = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"%d", a);
};
block();
}
return 0;
}
c++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
从c++实现中没有发现任何存储外部变量的信息, 可见block对于访问全局变量, 不会进行捕获, 直接访问
block的种类
要在MRC环境下使用以下代码, ARC环境下会将我们栈上的block拷贝到堆上
__NSGlobalBlock__
// __NSGlobalBlock__: 没有访问auto变量
void (^block)(void) = ^{
NSLog(@"global block");
};
NSLog(@"%@", [block class]);
__NSStackBlock__
// __NSStackBlock__: 访问了auto变量
int a = 10;
void (^block2)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", [block2 class]);
__NSMallocBlock__
// __NSMallocBlock__: __NSStackBlock__调用了copy
int b = 10;
void (^block3)(void) = [^{
NSLog(@"%d", b);
} copy];
NSLog(@"%@", [block3 class]);
注意: 当我们要持有block对象时, 要使用copy属性, 这样可以保证我们在使这个block时没有被销毁
__block修饰符
__block int a = 10;
void (^block)(void) = ^{
// 如果想要修改外部变量, 外部变量要用__block修饰
a = 20;
NSLog(@"%d", a);
};
block();
当基本数据类型的变量被__block修饰时, block内部会将其捕获为一个对象
c++代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__Block_byref_a_0 a = {0, &a, 0, sizeof(__Block_byref_a_0), 10};
void (*block)(void) = (
&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
&a,
570425344
)
);
block->FuncPtr(block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bs_8j14my3x2yxb7f_65thjrd1m0000gn_T_main_9aad1a_mii_1, (a.__forwarding->a));
}
return 0;
}
当一个基本类型的变量被block捕获之后, 它就不再是之前单纯的a了, a变得复杂了, 它变成了一个对象类型
__weak修饰符
循环引用的问题, 当代码执行完p对象不会被释放, 造成内存泄露
Person * p = [Person new];
void (^block)(void) = ^{
NSLog(@"block 内部 ------ %p", p);
};
p.block = block;
block();
NSLog(@"block 外部 ------");
使用__weak解决循环引用的问题
Person * p = [Person new];
__weak typeof(p) weakP = p;
void (^block)(void) = ^{
NSLog(@"block 内部 ------ %p", weakP);
};
p.block = block;
block();
NSLog(@"block 外部 ------");
p对象正常释放
__strong修饰符
如果在block内部想要在代码未执行完之前一直持有捕获的对象, 用该关键字修饰
Person * p = [Person new];
__weak typeof(p) weakP = p;
void (^block)(void) = ^{
__strong typeof(weakP) strongP = weakP;
NSLog(@"block 内部 ------ %p", strongP);
};
p.block = block;
block();
NSLog(@"block 外部 ------");
本篇博客纯粹是自己的学习笔记, 希望对自己的学习有一个总结性的成果, 如有错误的地方, 欢迎大家指正, 小弟不胜感激!