深入理解iOS的block (下)
对象类型的auto变量
例子一
首先看一个简单的例子
定义一个类 yzperson
,里面只有一个dealloc
方法
@interface yzperson : nsobject @property (nonatomic ,assign) int age; @end @implementation yzperson - (void)dealloc { nslog(@"%s",__func__); } @end
如下代码使用
int main(int argc, const char * argv[]) { @autoreleasepool { { yzperson *person = [[yzperson alloc]init]; person.age = 10; } nslog(@"-----"); } return 0; }
想必大家都能知道会输出什么,没错,就是person先销毁,然后打印-----
因为person是在大括号内,当大括号执行完之后,person 就销毁了。
ios-block[1376:15527] -[yzperson dealloc] ios-block[1376:15527] -----
例子二
上面的例子,是不是挺简单,那下面这个呢,
// 定义block typedef void (^yzblock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { yzblock block; { yzperson *person = [[yzperson alloc]init]; person.age = 10; block = ^{ nslog(@"---------%d", person.age); }; nslog(@"block.class = %@",[block class]); } nslog(@"block销毁"); } return 0; }
如下结果,输出可知当 block为__nsmallocblock__
类型时候,block可以保住person的命的,因为person离开大括号之后没有销毁,当block销毁,person才销毁
ios-block[3186:35811] block.class = __nsmallocblock__ ios-block[3186:35811] block销毁 ios-block[3186:35811] -[yzperson dealloc]
一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的ios交流群:1012951431, 分享bat,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。
分析
终端执行这行指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
把main.m
生成main.cpp
可以 看到如下代码
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* desc; yzperson *person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, yzperson *_person, int flags=0) : person(_person) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } };
很明显就是这个block里面包含 yzperson *person
。
mrc下 block引用实例对象
上面的例子,是不是挺简单,那如果是mrc下呢
// 定义block typedef void (^yzblock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { yzblock block; { yzperson *person = [[yzperson alloc]init]; person.age = 10; block = ^{ nslog(@"---------%d", person.age); }; nslog(@"block.class = %@",[block class]); // mrc下,需要手动释放 [person release]; } nslog(@"block销毁"); // mrc下,需要手动释放 [block release]; } return 0; }
输出结果为
ios-block[3114:34894] block.class = __nsstackblock__ ios-block[3114:34894] -[yzperson dealloc] ios-block[3114:34894] block销毁
和上面的对比,区别就是,还没有执行nslog(@"block销毁");
的时候,[yzperson dealloc]
已经执行了。也就是说,person 离开大括号,就销毁了。
输出可知当 block为__nsstackblock__
类型时候,block不可以保住person的命的
mrc下 [block copy]引用实例对象
在mrc下,对block执行了copy操作
// 定义block typedef void (^yzblock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { yzblock block; { yzperson *person = [[yzperson alloc]init]; person.age = 10; block = [^{ nslog(@"---------%d", person.age); } copy]; nslog(@"block.class = %@",[block class]); // mrc下,需要手动释放 [person release]; } nslog(@"block销毁"); [block release]; } return 0;
输出结果为,可知当 block为__nsmallocblock__
类型时候,block是可以保住person的命的
ios-block[3056:34126] block.class = __nsmallocblock__ ios-block[3056:34126] block销毁 ios-block[3056:34126] -[yzperson dealloc]
__weak
修饰
- 如下代码
// 定义block typedef void (^yzblock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { yzblock block; { yzperson *person = [[yzperson alloc]init]; person.age = 10; __weak yzperson *weakperson = person; block = ^{ nslog(@"---------%d", weakperson.age); }; nslog(@"block.class = %@",[block class]); } nslog(@"block销毁"); } return 0; }
- 输出为
ios-block[3687:42147] block.class = __nsmallocblock__ ios-block[3687:42147] -[yzperson dealloc] ios-block[3687:42147] block销毁
-
生成cpp文件
-
注意:
-
在使用clang转换oc为c++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference
-
解决方案:支持arc、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
生成之后,可以看到,如下代码,mrc情况下,生成的代码明显多了,这是因为arc自动进行了copy操作
//copy 函数 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); //dispose函数 void (*dispose)(struct __main_block_impl_0*);
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* desc; //weak修饰 yzperson *__weak weakperson; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, yzperson *__weak _weakperson, int flags=0) : weakperson(_weakperson) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } }; static struct __main_block_desc_0 { size_t reserved; size_t block_size; //copy 函数 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); //dispose函数 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 }; //copy函数内部会调用_block_object_assign函数 static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { //asssgin会对对象进行强引用或者弱引用 _block_object_assign((void*)&dst->person, (void*)src->person, 3/*block_field_is_object*/); } //dispose函数内部会调用_block_object_dispose函数 static void __main_block_dispose_0(struct __main_block_impl_0*src) { _block_object_dispose((void*)src->person, 3/*block_field_is_object*/); }
小结
无论是mac还是arc
- 当block为
__nsstackblock__
类型时候,是在栈空间,无论对外面使用的是strong 还是weak 都不会对外面的对象进行强引用 - 当block为
__nsmallocblock__
类型时候,是在堆空间,block是内部的_block_object_assign
函数会根据strong
或者weak
对外界的对象进行强引用或者弱引用。
其实也很好理解,因为block本身就在栈上,自己都随时可能消失,怎么能保住别人的命呢?
-
当block内部访问了对象类型的auto变量时
- 如果block是在栈上,将不会对auto变量产生强引用
-
如果block被拷贝到堆上
- 会调用block内部的copy函数
- copy函数内部会调用
_block_object_assign
函数 -
_block_object_assign
函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)
做出相应的操作,形成强引用(retain)或者弱引用
-
如果block从堆上移除
- 会调用block内部的dispose函数
- dispose函数内部会调用
_block_object_dispose
函数 -
_block_object_dispose
函数会自动释放引用的auto变量(release)
函数 | 调用时机 |
---|---|
copy函数 | 栈上的block复制到堆上 |
dispose函数 | 堆上的block被废弃时 |
__block
先从一个简单的例子说起,请看下面的代码
// 定义block typedef void (^yzblock)(void); int age = 10; yzblock block = ^{ nslog(@"age = %d", age); }; block();
代码很简单,运行之后,输出
age = 10
上面的例子在block中访问外部局部变量,那么问题来了,如果想在block内修改外部局部的值,怎么做呢?
修改局部变量的三种方法
写成全局变量
我们把a定义为全局变量,那么在哪里都可以访问,
// 定义block typedef void (^yzblock)(void); int age = 10; int main(int argc, const char * argv[]) { @autoreleasepool { yzblock block = ^{ age = 20; nslog(@"block内部修改之后age = %d", age); }; block(); nslog(@"block调用完 age = %d", age); } return 0; }
这个很简单,输出结果为
block内部修改之后age = 20 block调用完 age = 20
对于输出就结果也没什么问题,因为全局变量,是所有地方都可访问的,在block内部可以直接操作age的内存地址的。调用完block之后,全局变量age指向的地址的值已经被更改为20,所以是上面的打印结果
static修改局部变量
// 定义block typedef void (^yzblock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { static int age = 10; yzblock block = ^{ age = 20; nslog(@"block内部修改之后age = %d", age); }; block(); nslog(@"block调用完 age = %d", age); } return 0; }
上面的代码输出结果为
block内部修改之后age = 20 block调用完 age = 20
终端执行这行指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
把main.m
生成main.cpp
可以 看到如下代码
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; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *age = __cself->age; // bound by copy (*age) = 20; nslog((nsstring *)&__nsconstantstringimpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_t_main_5dbaa1_mi_0, (*age)); }
可以看出,当局部变量用static修饰之后,这个block内部会有个成员是int *age
,也就是说把age的地址捕获了。这样的话,当然在block内部可以修改局部变量age了。
- 以上两种方法,虽然可以达到在block内部修改局部变量的目的,但是,这样做,会导致内存无法释放。无论是全局变量,还是用static修饰,都无法及时销毁,会一直存在内存中。很多时候,我们只是需要临时用一下,当不用的时候,能销毁掉,那么第三种,也就是今天的主角
__block
隆重登场
__block
来修饰
代码如下
// 定义block typedef void (^yzblock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { __block int age = 10; yzblock block = ^{ age = 20; nslog(@"block内部修改之后age = %d",age); }; block(); nslog(@"block调用完 age = %d",age); } return 0; }
输出结果和上面两种一样
block内部修改之后age = 20 block调用完 age = 20
__block
分析
- 终端执行这行指令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
把main.m
生成main.cpp
首先能发现 多了__block_byref_age_0
结构体
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* desc; // 这里多了__block_byref_age_0类型的结构体 __block_byref_age_0 *age; // by ref // fp是函数地址 desc是描述信息 __block_byref_age_0 类型的结构体 *_age flags标记 __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; //fp是函数地址 desc = desc; } };
再仔细看结构体__block_byref_age_0
,可以发现第一个成员变量是isa指针,第二个是指向自身的指针__forwarding
// 结构体 __block_byref_age_0 struct __block_byref_age_0 { void *__isa; //isa指针 __block_byref_age_0 *__forwarding; // 指向自身的指针 int __flags; int __size; int age; //使用值 };
查看main函数里面的代码
// 这是原始的代码 __block_byref_age_0 __attribute__((__blocks__(byref))) __block_byref_age_0 age = { (void*)0,(__block_byref_age_0 *)&age, 0, sizeof(__block_byref_age_0), 10}; // 这是原始的 block代码 yzblock block = ((void (*)())&__main_block_impl_0( (void *)__main_block_func_0, &__main_block_desc_0_data, (__block_byref_age_0 *)&age, 570425344));
代码太长,简化一下,去掉一些强转的代码,结果如下
// 这是原始的代码 __block_byref_age_0 __attribute__((__blocks__(byref))) __block_byref_age_0 age = {(void*)0,(__block_byref_age_0 *)&age, 0, sizeof(__block_byref_age_0), 10}; //这是简化之后的代码 __block_byref_age_0 __block_byref_age_0 age = { 0, //赋值给 __isa (__block_byref_age_0 *)&age,//赋值给 __forwarding,也就是自身的指针 0, // 赋值给__flags sizeof(__block_byref_age_0),//赋值给 __size 10 // age 使用值 }; // 这是原始的 block代码 yzblock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_data, (__block_byref_age_0 *)&age, 570425344)); // 这是简化之后的 block代码 yzblock block = (&__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_data, &age, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->funcptr)((__block_impl *)block); //简化为 block->funcptr(block);
其中__block_byref_age_0
结构体中的第二个(__block_byref_age_0 *)&age
赋值给上面代码结构体__block_byref_age_0
中的第二个__block_byref_age_0 *__forwarding
,所以__forwarding
里面存放的是指向自身的指针
//这是简化之后的代码 __block_byref_age_0 __block_byref_age_0 age = { 0, //赋值给 __isa (__block_byref_age_0 *)&age,//赋值给 __forwarding,也就是自身的指针 0, // 赋值给__flags sizeof(__block_byref_age_0),//赋值给 __size 10 // age 使用值 };
结构体__block_byref_age_0
中代码如下,第二个__forwarding
存放指向自身的指针,第五个age
里面存放局部变量
// 结构体 __block_byref_age_0 struct __block_byref_age_0 { void *__isa; //isa指针 __block_byref_age_0 *__forwarding; // 指向自身的指针 int __flags; int __size; int age; //使用值 };
调用的时候,先通过__forwarding
找到指针,然后去取出age值。
(age->__forwarding->age));
小结
-
__block
可以用于解决block内部无法修改auto变量值的问题 -
__block
不能修饰全局变量、静态变量(static)- 编译器会将
__block
变量包装成一个对象
- 编译器会将
调用的是,从__block_byref_age_0
的指针找到 age
所在的内存,然后修改值
内存管理问题
bloc访问oc对象
代码如下
当block内部访问外面的oc对象的时候
eg:
// 定义block typedef void (^yzblock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { nsobject *obj = [[nsobject alloc]init]; yzblock block = ^{ nslog(@"%p",obj); }; block(); } return 0; }
在终端使用clang转换oc为c++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
因为是在arc下,所以会copy,栈上拷贝到堆上,结构体__main_block_desc_0
中有copy
和dispose
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*); }
copy
会调用 __main_block_copy_0
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*block_field_is_object*/);}
其内部的_block_object_assign
会根据代码中的修饰符 strong
或者weak
而对其进行强引用或者弱引用。
查看__main_block_impl_0
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* desc; //strong 强引用 nsobject *__strong obj; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, nsobject *__strong _obj, int flags=0) : obj(_obj) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } };
可以看上修饰符是strong
,所以,调用_block_object_assign
时候,会对其进行强引用。
由前面可知
-
当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内部的copy函数
- copy函数内部会调用
_block_object_assign
函数 -
_block_object_assign
函数会对__block
变量形成强引用(retain)
中我们知道,如下代码
- copy函数内部会调用
__block int age = 10; yzblock block = ^{ age = 20; nslog(@"block内部修改之后age = %d",age); };
局部变量age是在栈上的,在block内部引用age,但是当block从栈上拷贝到堆上的时候,怎么能保证下次block访问age的时候,能访问到呢?因为我们知道栈上的局部变量,随时会销毁的。
假设现在有两个栈上的block,分别是block0和block1,同时引用了了栈上的__block变量
。现在对block0进行copy操作,我们知道,栈上的block进行copy,就会复制到堆上,也就是说block0会复制到堆上,因为block0持有__block变量
,所以也会把这个__block变量
复制到堆上,同时堆上的block0对堆上的__block变量
是强引用,这样能达到block0随时能访问__block变量
。
还是上面的例子,刚才block0拷贝到堆上了,现在如果block1也拷贝到堆上,因为刚才变量已经拷贝到堆上,就不需要再次拷贝,只需要把堆上的block1也强引用堆上的变量就可以了。
释放
当释放的时候
- 会调用block内部的dispose函数
- dispose函数内部会调用
_block_object_dispose
函数 -
_block_object_dispose
函数会自动释放引用的__block变量(release)
- dispose函数内部会调用
上面的代码中,如果在堆上只有一个block引用__block变量
,当block销毁时候,直接销毁堆上的__block变量
,但是如果有两个block引用__block变量
,就需要当两个block都废弃的时候,才会废弃__block变量
。
其实,说到底,就是谁使用,谁负责
对象类型的auto变量
、__block
变量
把前面的都放在一起整理一下,有 auto 变量 num , __block
变量int, obj 和weakobj2如下
__block int age = 10; int num = 8; nsobject *obj = [[nsobject alloc]init]; nsobject *obj2 = [[nsobject alloc]init]; __weak nsobject *weakobj2 = obj2; yzblock block = ^{ nslog(@"age = %d",age); nslog(@"num = %d",num); nslog(@"obj = %p",obj); nslog(@"weakobj2 = %p",weakobj2); nslog(@"block内部修改之后age = %d",age); }; block();
执行终端指令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
生成代码如下所示
被__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)
- 会调用
__block
的__forwarding
指针
//结构体__block_byref_obj_0中有__forwarding struct __block_byref_obj_0 { void *__isa; __block_byref_obj_0 *__forwarding; int __flags; int __size; void (*__block_byref_id_object_copy)(void*, void*); void (*__block_byref_id_object_dispose)(void*); nsobject *__strong obj; }; // 访问的时候 age->__forwarding->age
为啥什么不直接用age,而是age->__forwarding->age
呢?
这是因为,如果__block
变量在栈上,就可以直接访问,但是如果已经拷贝到了堆上,访问的时候,还去访问栈上的,就会出问题,所以,先根据__forwarding
找到堆上的地址,然后再取值
总结
-
当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从堆上移除时,都会通过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的循环引用问题。
看如下代码,有个person类,里面两个属性,分别是block和age
#import <foundation/foundation.h> typedef void (^yzblock) (void); @interface yzperson : nsobject @property (copy, nonatomic) yzblock block; @property (assign, nonatomic) int age; @end #import "yzperson.h" @implementation yzperson - (void)dealloc { nslog(@"%s", __func__); } @end
main.m中如下代码
int main(int argc, const char * argv[]) { @autoreleasepool { yzperson *person = [[yzperson alloc] init]; person.age = 10; person.block = ^{ nslog(@"person.age--- %d",person.age); }; nslog(@"--------"); } return 0; }
输出只有
ios-block[38362:358749] ——–
也就是说程序结束,person都没有释放,造成了内存泄漏。
循环引用原因
下面这行代码,是有个person指针,指向了yzperson对象
yzperson *person = [[yzperson alloc] init];
执行完
person.block = ^{ nslog(@"person.age--- %d",person.age); };
之后,block内部有个强指针指向person,下面代码生成cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* desc; //强指针指向person yzperson *__strong person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, yzperson *__strong _person, int flags=0) : person(_person) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } };
而block是person的属性
@property (copy, nonatomic) yzblock block;
当程序退出的时候,局部变量person销毁,但是由于mjperson和block直接,互相强引用,谁都释放不了。
__weak
解决循环引用
为了解决上面的问题,只需要用__weak
来修饰,即可
int main(int argc, const char * argv[]) { @autoreleasepool { yzperson *person = [[yzperson alloc] init]; person.age = 10; __weak yzperson *weakperson = person; person.block = ^{ nslog(@"person.age--- %d",weakperson.age); }; nslog(@"--------"); } return 0; }
编译完成之后是
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* desc; // block内部对weakperson是弱引用 yzperson *__weak weakperson; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, yzperson *__weak _weakperson, int flags=0) : weakperson(_weakperson) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } };
当局部变量消失时候,对于yzpseson来说,只有一个若指针指向它,那它就销毁,然后block也销毁。
__unsafe_unretained
解决循环引用
除了上面的__weak
之后,也可以用__unsafe_unretained
来解决循环引用
int main(int argc, const char * argv[]) { @autoreleasepool { yzperson *person = [[yzperson alloc] init]; person.age = 10; __unsafe_unretained yzperson *weakperson = person; person.block = ^{ nslog(@"person.age--- %d",weakperson.age); }; nslog(@"--------"); } return 0; }
对于的cpp文件为
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* desc; yzperson *__unsafe_unretained weakperson; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, yzperson *__unsafe_unretained _weakperson, int flags=0) : weakperson(_weakperson) { impl.isa = &_nsconcretestackblock; impl.flags = flags; impl.funcptr = fp; desc = desc; } };
虽然__unsafe_unretained
可以解决循环引用,但是最好不要用,因为
-
__weak
:不会产生强引用,指向的对象销毁时,会自动让指针置为nil -
__unsafe_unretained
:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
__block
解决循环引用
eg:
int main(int argc, const char * argv[]) { @autoreleasepool { __block yzperson *person = [[yzperson alloc] init]; person.age = 10; person.block = ^{ nslog(@"person.age--- %d",person.age); //这一句不能少 person = nil; }; // 必须调用一次 person.block(); nslog(@"--------"); } return 0; }
上面的代码中,也是可以解决循环引用的。但是需要注意的是,person.block();
必须调用一次,为了执行person = nil;
.
对应的结果如下
- 下面的代码,block会对
__block
产生强引用
__block yzperson *person = [[yzperson alloc] init]; person.block = ^{ nslog(@"person.age--- %d",person.age); //这一句不能少 person = nil; };
- person对象本身就对block是强引用
@property (copy, nonatomic) yzblock block;
-
__block
对person产生强引用
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*); //`__block`对person产生强引用 yzperson *__strong person; };
所以他们的引用关系如图
当执行完person = nil
时候,__block
解除对person的引用,进而,全都解除释放了。
但是必须调用person = nil
才可以,否则,不能解除循环引用
小结
通过前面的分析,我们知道,arc下,上面三种方式对比,最好的是__weak
mrc下注意点
如果再mrc下,因为不支持弱指针__weak
,所以,只能是__unsafe_unretained
或者__block
来解决循环引用
结束
回到最开始的问题
-
block的原理是怎样的?本质是什么?
-
__block
的作用是什么?有什么使用注意点? -
block的属性修饰词为什么是copy?使用block有哪些使用注意?
-
block一旦没有进行copy操作,就不会在堆上
-
block在修改nsmutablearray,需不需要添加__block?
现在是不是心中有了自己的答案呢?
另外,如果你想一起进阶,不妨添加一下交流群1012951431,选择加入一起交流,一起学习。期待你的加入!