基本认识
block本质上是一个封装了函数调用以及函数调用环境的OC对象,内部也包含一个isa指针。
block的底层实现
简单的block底层实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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; } };
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; //函数指针 };
static struct __main_block_desc_0 { size_t reserved; size_t Block_size; //内存占用 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
|
变量捕获-基础数据类型
auto变量
定义如下一个捕获auto类型变量(离开作用域自动销毁)的block:
1 2 3 4
| int age = 10; void (^block)(void) = ^(){ NSLog(@"%d",age); };
|
将其转换为c++代码:
1 2
| int age = 10; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
|
block底层结构为:
1 2 3 4 5 6 7 8 9 10 11
| struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
|
可见,block在捕获auto变量的方式为值传递。
static变量
定义如下一个捕获static类型变量的block:
1 2 3 4
| static int age = 10; void (^block)(void) = ^(){ NSLog(@"%d",age); };
|
将其转换为c++代码:
1 2
| static int age = 10; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));
|
block底层结构为:
1 2 3 4 5 6 7 8 9 10 11
| struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *age; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
|
可见,block在捕获static变量的方式为指针传递。
全局变量
定义如下一个捕获全局变量的block:
1 2 3 4
| //age此时被定义为全局变量 void (^block)(void) = ^(){ NSLog(@"%d",age); };
|
将其转换为c++代码:
1 2
| void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
|
block底层结构为:
1 2 3 4 5 6 7 8 9 10
| 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捕获。
总结
变量捕获-对象类型
当block内部访问了对象类型的auto变量时:
此时block的底层相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/); }
static void __main_block_dispose_0(struct __main_block_impl_0*src) { _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/); }
|
block的类型
blcok有三种类型,都继承自NSBlock。可以通过class方法或者isa指针查看其具体类型。
- __NSGlobalBlock__ (_NSConcreteGlobalBlock) :没有访问auto变量
- __NSStackBlock__ (_NSConcreteStackBlock : 访问了auto变量
- __NSMallocBlock__ (_NSConcreteMallocBlock): __NSStackBlock__调用了copy方法
下面代码的输出结果为:__NSGlobalBlock__
1 2 3 4 5
| void (^block)(void) = ^(){ NSLog(@"Hello World"); }; NSLog(@"%@",[block class]);
|
下面代码ARC环境下的输出结果为:__NSMallocBlock__,MRC的输出结果为:__NSStackBlock__
1 2 3 4 5 6
| int age = 10; void (^block)(void) = ^(){ NSLog(@"%d",age); }; NSLog(@"%@",[block class]);
|
下面代码的输出结果为:__NSStackBlock__
1 2 3 4
| int age = 10; NSLog(@"%@",[^{ NSLog(@"%d",age); } class]);
|
三种类型block的内存存储以及每一种类型的block调用copy后的结果如下所示:
__block修饰符
底层实现
__block可以用于解决block内部无法修改auto变量值的问题。__block不能修饰全局变量、静态变量(static)。
1 2 3 4 5 6 7
| __block int age = 10; __block NSObject *obj = [[NSObject alloc] init];
void (^block)(void) = ^{ obj = nil; age = 20; };
|
上面的代码会被编译为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_obj_1 *obj; // by ref __Block_byref_age_0 *age; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
struct __Block_byref_age_0 { void *__isa; __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; };
struct __Block_byref_obj_1 { void *__isa; __Block_byref_obj_1 *__forwarding; int __flags; int __size; //内存管理 void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSObject *obj; };
//实际执行的block代码 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_obj_1 *obj = __cself->obj; // bound by ref __Block_byref_age_0 *age = __cself->age; // bound by ref
(obj->__forwarding->obj) = __null; (age->__forwarding->age) = 20; }
|
可见,__block修饰的变量会被包装成一个对象。
__block变量的内存管理
当block在栈上时,并不会对__block变量产生强引用。
当block被copy到堆时:
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会对__block变量形成强引用(retain)[仅ARC, MRC不会retain]
当block从堆中移除时:
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的__block变量(release)
__forwarding指针
为什么会指向自己呢,原因是为了确保当栈中的Block复制到堆中的时候,在栈中仍然能正确访问堆中的变量。
循环引用
循环引用产生的原因:
解决循环引用:
- ARC: __weak、__unsafe_unretained
- MRC: __block