iOS底层-Block系列二
block的copy
- 在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
- MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
- ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
-
代码举例:
//1. block作为函数返回值 typedef void(^Block)(void); Block myblocfunc(){ int a = 10; //这个block本来是一个栈block return ^{ NSLog(@"===%d",a); }; } int main(int argc, const char * argv[]) { @autoreleasepool { //打印结果:====__NSMallocBlock__ //说明变为堆block // NSLog(@"====%@",[myblocfunc() class]); //2. 强引用 //OC中,定义的变量就是一个强引用 int a = 0; Block block2 = ^{ NSLog(@"====%d",a); }; //ARC: __NSMallocBlock__ MRC: __NSStackBlock__ NSLog(@"====%@",[block2 class]); //3. Cocoa API中方法名含有usingBlock的方法参数时 NSArray *arr = @[]; [arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { }]; //4. 作为GCD API的方法参数时 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ }); } return 0; }
block对 对象类型的auto变量 的捕获
- 当block内部访问了对象类型的auto变量时
- 如果block是在栈上,将不会对auto变量产生强引用,auto变量会在block之前释放
- 如果block被拷贝到堆上
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作(如果auto变量是强引用,就retain),形成强引用(retain)或者弱引用
- 如果block从堆上移除
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的auto变量(release)
-
如下示例:
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { //定义一个block变量,在ARC下,任何一个局部变量都是强引用__strong //在MRC下是弱引用 void (^block)(void);//定义block变量 //作用域 { //对象类型的auto变量 Person *person = [[Person alloc] init]; person.age =10; //弱引用 //__weak Person *weakPerson = person; //ARC下这个block会调用copy,放到堆上; MRC下这个block在栈上 block = ^{ NSLog(@"====%d",person.age); }; } //此处打断点 NSLog(@"---------------"); } return 0; }
- 如果代码中有__weak字段,转化为C++代码时注意:
- 使用原来的方式会报错:
cannot create __weak reference in file using manual reference
- 解决方案:支持ARC、指定运行时系统版本,比如:
pxcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
- 使用原来的方式会报错:
__block的本质
-
block内部能够修改外部的变量吗?
//int age = 10; //能修改,本质是全局变量可以在任何地方访问 int main(int argc, const char * argv[]) { @autoreleasepool { // int age = 10; //这种不能修改,因为本质是值传递 // static int age = 10; //能修改,因为本质是地址传递 __block int age = 10; //能修改,为什么呢? void (^block)(void) = ^{ age = 20; NSLog(@"======%d",age); }; block(); } return 0; }
- block内部不可以修改自动变量
- block内部可以修改全局变量、静态变量
- block内部可以修改__block修饰的自动变量,为什么呢?
- __block作用:
- __block可以用于解决block内部无法修改auto变量值的问题
- __block不能修饰全局变量、静态变量(static)
-
将上面的代码编译成C++,核心代码如下:
//与之前相比多了这个对象__Block_byref_age_0 struct __Block_byref_age_0 { void *__isa; __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //不再是int age; __Block_byref_age_0 *age; // by ref //初始化列表是将构造函数的_age的__forwarding成员,赋值给当前的age成员,而不是age = _age; __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; } }; //fun函数 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_age_0 *age = __cself->age; // bound by ref (age->__forwarding->age) = 20; NSLog((NSString *)&__NSConstantStringImpl__var_folders_t2_d94848jx1fq4vd6k8qcsfdq00000gn_T_main_831572_mi_0,(age->__forwarding->age)); } //copy函数 static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/); } //dispose函数 static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);} 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}; int main(int argc, const char * argv[]) { { __AtAutoreleasePool __autoreleasepool; //__block int age = 10; __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
-
__block int age = 10;
这句代码会转变成如下:__Block_byref_age_0 age = { (void*)0, (__Block_byref_age_0 *)&age, //用对象本质分析 0, sizeof(__Block_byref_age_0), 10};
-
__Block_byref_age_0
是一个结构体对象:struct __Block_byref_age_0 { void *__isa; __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; };
-
block多的不在是一个int age类型的成员变量,而是
__Block_byref_age_0
类型的成员变量struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //不再是int age; __Block_byref_age_0 *age; // by ref //初始化列表是将构造函数的_age的__forwarding成员,赋值给当前的age成员,而不是age = _age; __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; } };
-
结论:
- __block修饰的自动变量会封装成一个__Block_byref_age_0类型的结构体对象
- age的真实值保存在__Block_byref_age_0类型对象的成员age中
- 调用block时,就是调用__main_block_func_0函数,通过__Block_byref_age_0类型的对象可以找到age的值,然后修改即可
-
__block的内存管理
- 当block在栈上时,并不会对__block变量产生强引用
- 当block被copy到堆时
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会对__block变量形成强引用(retain)
- 当block从堆中移除时
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的__block变量(release)
被__block修饰的对象类型
- 当__block变量在栈上时,不会对指向的对象产生强引用
- 当__block变量被copy到堆时
- 会调用__block变量内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
- 如果__block变量从堆上移除
- 会调用__block变量内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放指向的对象(release)
对象类型的auto变量、__block变量
- 当block在栈上时,对它们都不会产生强引用
- 当block拷贝到堆上时,都会通过copy函数来处理它们
- _block变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
- 对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
- _block变量(假设变量名叫做a)
- 当block从堆上移除时,都会通过dispose函数来释放它们
- __block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
- 对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
- __block变量(假设变量名叫做a)
循环引用
- 现象
- 对象持有block,block持有对象(self)
- 解决循环引用问题
- ARC环境
-
用
__weak、__unsafe_unretained
解决Person *person = [[Person alloc] init]; person.age = 10; //不会产生强引用,安全:指向的对象销毁时,会设置为nil //__weak Person *weakPerson = person; //不会产生强引用,不安全:指向的对象销毁时,不会置nil,再次使用会野指针错误 __unsafe_unretained Person *unsafePerson = person; person.myblock = ^{ NSLog(@"=====%d",unsafePerson.age); };
-
用
__block
解决(必须要调用block)__block Person *weakSelf = person; person.myblock = ^{ NSLog(@"=====%d",weakSelf.age); weakSelf = nil; }; //必须调用block person.myblock();
-
- MRC环境
-
用__unsafe_unretained解决(MRC不支持weak)
Person *person = [[Person alloc] init]; person.age = 10; __unsafe_unretained Person *unsafePerson = person; person.myblock = ^{ NSLog(@"=====%d",unsafePerson.age); }; [person release];
-
用__block解决
Person *person = [[Person alloc] init]; person.age = 10; //MRC情况下不会对__block的变量产生强引用!!! __block Person *weakSelf = person; person.myblock = ^{ NSLog(@"=====%d",weakSelf.age); weakSelf = nil; }; [person release];
-
- ARC环境
面试题 - Block
- block的原理是怎样的?本质是什么?
- 封装了函数调用以及调用环境的OC对象
- __block的作用是什么?有什么使用注意点?
- 将__block修饰的变量包装成一个对象,以达到可以修改局部自动变量的功能
- 注意: 在MRC环境下,__block修饰的变量不会产生强引用
- block的属性修饰词为什么是copy?使用block有哪些使用注意?
- block一旦没有进行copy操作,就不会在堆上
- 使用注意:循环引用问题
-
block在修改NSMutableArray,需不需要添加__block?-不需要
void test(){ NSMutableArray *array = [NSMutableArray array]; void (^block)(void) = ^{ //这个不是修改array指针,而是使用array指针,所以可以 [array addObject:@(2)]; }; }