之前我们探讨了Block捕获基本数据变量的实现原理,对于对象类型变量的捕获原理是不是跟基本数据变量一样呢?
3.捕获对象类型变量
我们继续使用之前介绍过的clang命令来重写OC代码来查看转化之后的C++代码来探讨Block对于对象类型的捕获原理.我们使用自定义Person 类来做说明:
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end
@implementation Person
@end
3.1 Block捕获静态变量或者全局变量
3.1.1 捕获静态变量
[示例代码3.1.1]
static Person *person; person = [[Person alloc] init]; person.name = @"Jack"; void(^block)(void) = ^(){ person.name = @"Frank"; }; block(); NSLog(@"person.name == %@", person.name);
我们将转化之后的代码进行删减[之前已经出现过的部分重复结构体不再列出]和重新排列[方便查看]:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person **person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person **_person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { Person **person = __cself->person; // bound by copy ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_1); } 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*/);} 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[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; static Person *person; person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_0); void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &person, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_2, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("name"))); } return 0; }
去除main()函数中的各种强制转化,可以更加清晰地查看调用关系:
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; static Person *person; //使用运行时方式消息机制调用方法,创建Person对象 person = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")); //调用setName:方法 objc_msgSend(person, sel_registerName("setName:"), &__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_0); //初始化__main_block_impl_0实例block block = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &person, 570425344); //调用block的FunPtr实现 block->FuncPtr(block); NSLog(&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_2, objc_msgSend(person, sel_registerName("name")); //打印消息 } return 0; }
在struct __main_block_impl_0的实现中:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person **person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person **_person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
我们发现了一个用于存储person变量的二级指针变量,所以在Block 中不仅可以修改Person 的成员变量,还可以对person变量重新赋值.
在Block捕获对象对象类型变量时,我们struct __main_block_desc_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*/);} 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};
- __main_block_copy_0:当block进行copy操作的时候就会自动调用,主要用来根据flags参数处理Block捕获的对象变量是否需要强引用.底层是通过调用_Block_object_assign来实现的,该函数的的原型有三个参数:
- void *destAddr:需要进行copy操作的新对象(copy之后的对象)地址;
- const void *object:需要进行copy操作的原始对象(copy之前的对象)地址;
- const int flags:对象标识位,是一个用于标记对象类型枚举值,用于判断标志是否需要对Block捕获的对象进行强引用.BLOCK_FIELD_IS_OBJECT表示是一个普通的对象,BLOCK_FIELD_IS_BLOCK是一个Block变量,BLOCK_FIELD_IS_BYREF是一个被__block修饰的变量,BLOCK_FIELD_IS_WEAK表示是一个使用__weak修饰的变量,BLOCK_BYREF_CALLER表示一个使用内部copy/dispose进行管理的辅助函数,不需要进行引用计数操作.
// Values for _Block_object_assign() and _Block_object_dispose() parameters enum { // see function implementation for a more complete description of these fields and combinations BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ... BLOCK_FIELD_IS_BLOCK = 7, // a block variable BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines. }; enum { BLOCK_ALL_COPY_DISPOSE_FLAGS = BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER }; // Runtime entry point called by compiler when assigning objects inside copy helper routines BLOCK_EXPORT void _Block_object_assign(void *destAddr, const void *object, const int flags); // BLOCK_FIELD_IS_BYREF is only used from within block copy helpers
- __main_block_dispose_0:当block从堆中移除时就会自动调用,主要用于处理对捕获对象的release操作,断开对捕获对象的强引用操作,底层通过_Block_object_dispose实现.
// runtime entry point called by the compiler when disposing of objects inside dispose helper routine BLOCK_EXPORT void _Block_object_dispose(const void *object, const int flags);
3.1.2 捕获全局变量
Person *person; int main(int argc, const char * argv[]) { @autoreleasepool { person = [[Person alloc] init]; person.name = @"Jack"; void(^block)(void) = ^(){ person.name = @"Frank"; }; block(); } return 0; }
使用clang命令进行重写:
Person *person; 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; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b01edc_mii_2); } 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)}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b01edc_mii_1); 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; }
对于全局变量,Block并没有进行任何额外的操作,毕竟全局普通变量Block作用域享有与其他函数同等的操作权限.
3.2 捕获不使用__block修饰的对象变量
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person *person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { Person *person = __cself->person; // bound by copy ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b041db_mii_2); } 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*/);} 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[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; Person *person; person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b041db_mii_1); void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b041db_mii_3, block); } return 0; }
在Block的实现中,将Person的地址传入Block的构造函数中,所以在Block可以修改person指向空间的值,也就是各个成员变量的值,但是不可以修改person指针地址.
3.3 捕获__block对象变量
int main(int argc, const char * argv[]) { @autoreleasepool { __block Person *person; person = [[Person alloc] init]; person.name = @"Jack"; void(^block)(void) = ^(){ person.name = @"Frank"; }; block(); } return 0; }
使用clang命令进行重写
struct __Block_byref_person_0 { void *__isa; __Block_byref_person_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); Person *person; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_person_0 *person; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_person_0 *person = __cself->person; // bound by ref ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_c45b4a_mii_2); } 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, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131}; ; (person.__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)(person.__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_c45b4a_mii_1); void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_c45b4a_mii_3, block); } return 0; }
在这个实现中,出现了新的结构体:
struct __Block_byref_person_0 { void *__isa; __Block_byref_person_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); Person *person; };
- 使用__block修饰的变量被封装了一个struct __Block_byref_person_0类型的结构体,同时将内部的__forwarding指针指向了自己的结构体地址,在之后关于person的操作中,无论是Block内部还是外部,其实都是使用该结构体来进行person变量的相关操作,这就保证person变量数据操作的一致性.此时就可以通过struct __Block_byref_person_0结构体来实现捕获变量的属性修改以及变量本身的重新赋值等;
- 这时候的变量的存储以及释放操作不再由Block本身的copy/dispose来控制,而是由struct __Block_byref_person_0结构体的copy/dispose来控制;
- 初始化时__Block_byref_id_object_copy_131时,使用了地址偏移量的形式来获取成员变量:
static void __Block_byref_id_object_copy_131(void *dst, void *src) { _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131); }
其中(char*)dst + 40 就是Person *person的首地址(因为在64位系统中,int占据四个字节, void *占据八个字节, 所以40=8(void *)+8(__Block_byref_person_0 *) + 4( int) + 4( int) + 8(void *) + 8 (void *)),而131=128+3=BLOCK_BYREF_CALLER|BLOCK_BYREF_IS_OBJECT.
4. __block对对象变量生命周期(引用计数)的影响?
自定义一个NSObject的分类,在Targets-Build Phases中将该文件设置为-fno-objc-arc:
@interface NSObject (RetainCount) - (NSInteger)getRetainConut; @end @implementation NSObject (RetainCount) - (NSInteger)getRetainConut { return self.retainCount; }
同时将在Person中重写dealloc方法:
@interface Person : NSObject @property (copy, nonatomic) NSString *name; @end @implementation Person - (void)dealloc { NSLog(@"对象开始释放"); } @end
然后我们尝试输出对象的引用计数:
__block Person *person; person = [[Person alloc] init]; person.name = @"Jack"; NSLog(@"retainCount == %ld", [person getRetainConut]); void(^block)(void) = ^(){ person.name = @"Frank"; }; NSLog(@"retainCount == %ld", [person getRetainConut]); block();
输出结果:
retainCount == 1 retainCount == 1 对象开始释放
其实从3.1.3的重写代码中我们可以看到如果使用了__block来修饰对象变量那么对象变量会被封装成新的结构体,通过该结构体的内部实现来控制对象的生命周期.所以在Block调用为结束之前,该变量是不会被释放的.来做个简单验证,将block的执行改在3s之后:
__block Person *person; person = [[Person alloc] init]; person.name = @"Jack"; NSLog(@"retainCount == %ld", [person getRetainConut]); void(^block)(void) = ^(){ person.name = @"Frank"; }; NSLog(@"retainCount == %ld", [person getRetainConut]); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ block(); });
输入结果:
17:05:12.599798+0800 BlockDemo[49800:10999998] retainCount == 1 17:05:12.599929+0800 BlockDemo[49800:10999998] retainCount == 1 17:05:15.600426+0800 BlockDemo[49800:10999998] 对象开始释放
所以我们可以看到在当前person定义的作用域结束时,person并没有被释放,而是在3s之后Block执行结束之后person 对象才被释放.所以在使用__block时不用担心局部对象变量会被提前释放,因为Block强引用了对象变量,只有等到Block的生命周期结束之后有可能会被释放.
如果我们不使用__block修饰会出现什么现象呢?
Person *person; person = [[Person alloc] init]; person.name = @"Jack"; NSLog(@"retainCount == %ld", [person getRetainConut]); void(^block)(void) = ^(){ person.name = @"Frank"; }; NSLog(@"retainCount == %ld", [person getRetainConut]); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ block(); });
输出结构:
17:11:05.834223+0800 BlockDemo[49853:11005986] retainCount == 1 17:11:05.834365+0800 BlockDemo[49853:11005986] retainCount == 3 17:11:08.834791+0800 BlockDemo[49853:11005986] 对象开始释放
同样,person对象需要在Block执行完成之后才可以被释放,而且Block的出现对于对象变量明显产生了强引用.根据我们3.2的从写代码分析可知,
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person *person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
不使用__block修饰时,在Block内部实现结构体中引用了person对象,同时使用__main_block_impl_0中的__main_block_desc_0中的辅助copy函数来管理引用对象的引用计数,所以在Block的实现中,我们可以发现Block其实是强引用了局部对象.这是因为其实每个变量默认的修饰符在缺省时默认是__strong,所以Block强引用了person对象,所以在Block执行期间,person对象不会被释放.
那么如果此时这个外部对象本身直接或者间接强引用了block,两个对象相互强引用了彼此会怎么样呢?会出现循环引用问题.
5. 循环引用问题
循环引用,就是指由于对象之间相互持有,而导致对象占用的空间不能及时进行释放,从而导致内存泄漏的现象.常见的循环引用问题,主要存在于两类:
- 两个对象之间直接或者间接相互持有:例如
@class Son; @interface Father : NSObject @property (strong, nonatomic) Son *son; @end @implementation Father - (void)dealloc { NSLog(@"Father开始释放!"); } @end @interface Son : NSObject @property (strong, nonatomic) Father *father; @end @implementation Son - (void)dealloc { NSLog(@"Son开始释放!"); } @end //在使用中出现这样的代码 Father *father = [[Father alloc] init]; Son *son = [[Son alloc] init]; father.son = son; son.father = father;
这种情况的主要解决方案是将其中一个属性生命为weak,至于将哪个属性声明为weak要看具体的需要.常见的应用场景还有控制器有一个UITableView对象属性,而UITableView对象又将控制器设置为代理.
- 对象属性中存在Block属性,而Block又间接使用了当前对象.例如,当前控制器有一个camera属性用来自定义相机,当摄像机闪光灯打开时需要将界面上的btn_flash.selected = true,否则btn_flash.selected = false:
@interface ViewController () @property (strong, nonatomic) Camera *camera; @property (strong, nonatomic) UIButton *btn_flash; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.camera = [[Camera alloc] init]; self.camera.didStatusChange = ^(BOOL off) { self.btn_flash.selected = off; }; }
由于camera强引用了回调didStatusChange,而didStatusChange在定义时又强引用了当前控制器self,导致相互引用计数啊永远不为零,造成空间无法释放.这种循环引用比较隐蔽,不太容易发现,平时需要多注意.解决这类循环引用问题,方法很多,我们这里介绍两种:
(1) 我们常用的处理方法是使用__weak方法来告诉Block不要对捕获的对象进行强引用,破坏循环引用的闭环,使对象在适当的时候进行释放.
@interface ViewController () @property (strong, nonatomic) Camera *camera; @property (strong, nonatomic) UIButton *btn_flash; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.camera = [[Camera alloc] init]; __weak typeof(self) weakSelf = self; //或者直接使用 __weak ViewController *weakSelf = self; self.camera.didStatusChange = ^(BOOL off) { weakSelf.btn_flash.selected = off; }; }
这里需要注意的是:使用__weak之后,Block不再强引用捕获对象,所以在Block执行时,需要确认引用到的对象依然没有被销毁,否则该对象的引用就没有任何意义了.虽然这种情况并不常用,但是如果你有足够的理由需要这么做,可以使用再使用__strong 关键字对引用对象进行一次强引用来延长引用对象的生命周期,避免提前释放.所以我们也可以得出一个注意事项,那就是在没有循环引用的情况下,Block会自动处理捕获对象的生命周期问题,保证在Block执行时引用对象是未被释放的,所以尽量不要人为的干预.
@interface ViewController () @property (strong, nonatomic) Camera *camera; @property (strong, nonatomic) UIButton *btn_flash; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.camera = [[Camera alloc] init]; __weak typeof(self) weakSelf = self; //或者直接使用 __weak ViewController *weakSelf = self; self.camera.didStatusChange = ^(BOOL off) { __strong strongSelf = weakSelf; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog("self == %@", strongSelf); strongSelf.btn_flash.selected = off; //code here to do what you want related self }); }; }
使用__strong 关键字可以使strongSelf这个变量强引用weakSelf变量,使得在新的Block在生命周期内,局部变量strongSelf对weakSelf的持有不释放.当新的Block执行完毕之后,strongSelf对wealSelf的强持有被释放,此时self对象才有可能被释放.
(2)根据上边的操作我们可以获得新的启示,所以对于Block的循环引用问题,我们可以有一种新的方法:即在Block中使用一个新Block来捕获临时变量,既可以保证在Block运行期间引用对象不释放,又可以保证不产生循环引用.
void(^block)(ViewController *, BOOL) = ^(ViewController *vc,BOOL off) { NSLog(@"self == %@", vc); vc.btn_flash.selected = off; //code here to do what you want related self(vc) }; __weak typeof (self) weakSelf = self; self.callback = ^(BOOL off){ __strong typeof(weakSelf) strongSelf = weakSelf; block(strongSelf, off); };
在这种操作中,由于strongSelf是一个局部变量,所以被block捕获之后会保证在blcok的生命周期内strongSelf不会被释放,当block执行结束之后就会释放掉strongSelf,同时断开对于weakSelf的强引用,这时候self对象是否释放就跟block没有关系了.由于这里的block操作只是一个过渡,所以可以继续简化一下,让block变成匿名实现:
__weak typeof (self) weakSelf = self; self.callback = ^(BOOL off){ __strong typeof(weakSelf) strongSelf = weakSelf; ^(ViewController *vc, BOOL off){ NSLog(@"self == %@", vc); vc.btn_flash.selected = off; //code here to do what you want related self }(strongSelf, off); };
当然,其实之所以循环引用会不能释放空间,是因为彼此之间的持有不能正常释放,如果我们可以手动将其中一个的持有关系打破即可.例如,我们可以这样(我们手动将强引用临时变量置为nil,断开其中的强引用):
void(^block)(ViewController *, BOOL) = ^(ViewController *vc,BOOL off) { NSLog(@"self == %@", vc); vc = nil; //code here to do what you want related self }; __block typeof (self) vc = self; self.callback = ^(BOOL off){ block(vc, off); };