__weak与__block区别,深层理解两者区别
准备工作
首先我定义了一个类 MyObject
继承 NSObject
,并添加了一个属性 text,重写了description
方法,返回 text 的值。这个主要是因为编译器本身对 NSString 是有优化的,创建的 string 对象有可能是静态存储区永不释放的,为了避免使用 NSString 引起一些问题,还是创建一个 NSObject 对象比较合适。
另外我自定义了一个 TLog 方法输出对象相关值,定义如下:
#define TLog(prefix,Obj) {NSLog(@"变量内存地址:%p, 变量值:%p, 指向对象值:%@, --> %@",&Obj,Obj,Obj,prefix);}
__weak
MyObject *obj = [[MyObject alloc]init]; obj.text = @"my-object"; TLog(@"obj", obj); __weak MyObject *weakObj = obj; TLog(@"weakObj", weakObj); void(^testBlock)() = ^(){ TLog(@"weakObj - block", weakObj); }; testBlock(); obj = nil; testBlock();
变量内存地址:0x7fff58c8a9f0, 变量值:0x7f8e0307f1d0, 指向对象值:my-object, --> obj 变量内存地址:0x7fff58c8a9e8, 变量值:0x7f8e0307f1d0, 指向对象值:my-object, --> weakObj 变量内存地址:0x7f8e030804c0, 变量值:0x7f8e0307f1d0, 指向对象值:my-object, --> weakObj - block 变量内存地址:0x7f8e030804c0, 变量值:0x0, 指向对象值:(null), --> weakObj - block
从上面的结果可以看到
- block 内的 weakObj 和外部的 weakObj 并不是同一个变量
- block 捕获了 weakObj 同时也是对 obj 进行了弱引用,当我在 block 外把 obj 释放了之后,block 内也读不到这个变量了
- 当 obj 赋值 nil 时,block 内部的 weakObj 也为 nil 了,也就是说 obj 实际上是被释放了,可见
__weak
是可以避免循环引用问题的
接下来我们再看第二段代码
MyObject *obj = [[MyObject alloc]init]; obj.text = @"my-object"; TLog(@"obj", obj); __weak MyObject *weakObj = obj; TLog(@"weakObj-0", weakObj); void(^testBlock)() = ^(){ __strong MyObject *strongObj = weakObj; TLog(@"weakObj - block", weakObj); TLog(@"strongObj - block", strongObj); }; TLog(@"weakObj-1", weakObj); testBlock(); TLog(@"weakObj-2", weakObj); obj = nil; testBlock(); TLog(@"weakObj-3", weakObj);
变量内存地址:0x7fff5d7b2d18, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> obj 变量内存地址:0x7fff5d7b2d10, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj-0 变量内存地址:0x7fff5d7b2d10, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj-1 变量内存地址:0x7fcf78f0f520, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj - block 变量内存地址:0x7fff5d7b2bb8, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> strongObj - block 变量内存地址:0x7fff5d7b2d10, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj-2 变量内存地址:0x7fcf78f0f520, 变量值:0x0, 指向对象值:(null), --> weakObj - block 变量内存地址:0x7fff5d7b2bb8, 变量值:0x0, 指向对象值:(null), --> strongObj - block 变量内存地址:0x7fff5d7b2d10, 变量值:0x0, 指向对象值:(null), --> weakObj-3
如果你看过 AFNetworking 的源码,会发现 AFN 中作者会把变量在 block 外面先用 __weak
声明,在 block 内把前面 weak 声明的变量赋值给 __strong
修饰的变量这种写法。
从上面例子我们看到即使在 block 内部用 strong 强引用了外面的 weakObj ,但是一旦 obj 释放了之后,内部的 strongObj 同样会变成 nil,那么这种写法又有什么意义呢?
下面再看一段代码:
MyObject *obj = [[MyObject alloc]init]; obj.text = @"my-object"; TLog(@"obj", obj); __weak MyObject *weakObj = obj; TLog(@"weakObj-0", weakObj); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ __strong MyObject *strongObj = weakObj; TLog(@"weakObj - block", weakObj); TLog(@"strongObj - block", strongObj); sleep(3); TLog(@"weakObj - block", weakObj); TLog(@"strongObj - block", strongObj); }); NSLog(@"------ sleep 1s"); sleep(1); obj = nil; TLog(@"weakObj-1", weakObj); NSLog(@"------ sleep 5s"); sleep(5); TLog(@"weakObj-2", weakObj);
变量内存地址:0x7fff58e2ad18, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> obj 变量内存地址:0x7fff58e2ad10, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj-0 变量内存地址:0x7fa2b1e80710, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj - block 变量内存地址:0x700000093de8, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> strongObj - block ------ sleep 1s 变量内存地址:0x7fff58e2ad10, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj-1 ------ sleep 5s 变量内存地址:0x7fa2b1e80710, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj - block 变量内存地址:0x700000093de8, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> strongObj - block 变量内存地址:0x7fff58e2ad10, 变量值:0x0, 指向对象值:(null), --> weakObj-2
代码中使用 sleep 来保证代码执行的先后顺序。
从结果中我们可以看到,只要 block 部分执行了,即使我们中途释放了 obj,block 内部依然会继续强引用它。对比上面代码,也就是说 block 内部的 __strong 会在执行期间进行强引用操作,保证在 block 内部 strongObj 始终是可用的。这种写法非常巧妙,既避免了循环引用的问题,又可以在 block 内部持有该变量。
综合两部分代码,我们平时在使用时,常常先判断 strongObj 是否为空,然后再执行后续代码,如下方式:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ __strong MyObject *strongObj = weakObj; if (strongObj) { // do something ... } });
这种方式先判断 Obj 是否被释放,如果未释放在执行我们的代码的时候保证其可用性。
__block
先上代码
MyObject *obj = [[MyObject alloc]init]; obj.text = @"my-object-1"; TLog(@"obj",obj); __block MyObject *blockObj = obj; obj = nil; TLog(@"blockObj -1",blockObj); void(^testBlock)() = ^(){ TLog(@"blockObj - block",blockObj); MyObject *obj2 = [[MyObject alloc]init]; obj2.text = @"my-object-2"; TLog(@"obj2",obj2); blockObj = obj2; TLog(@"blockObj - block",blockObj); }; NSLog(@"%@",testBlock); TLog(@"blockObj -2",blockObj); testBlock(); TLog(@"blockObj -3",blockObj);
变量内存地址:0x7fff5021a9f0, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-1, --> obj 变量内存地址:0x7fff5021a9e8, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-1, --> blockObj -1 <__NSMallocBlock__: 0x7ff6b48d8c20> 变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-1, --> blockObj -2 变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-1, --> blockObj - block 变量内存地址:0x7fff5021a7f8, 变量值:0x7ff6b48d9960, 指向对象值:my-object-2, --> obj2 变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d9960, 指向对象值:my-object-2, --> blockObj - block 变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d9960, 指向对象值:my-object-2, --> blockObj -3
可以看到在 block 声明前后 blockObj 的内存地址是有所变化的,这涉及到 block 对外部变量的内存管理问题。
下面来看看 __block
能不能避免循环引用的问题
MyObject *obj = [[MyObject alloc]init]; obj.text = @"11111111111111"; TLog(@"obj",obj); __block MyObject *blockObj = obj; obj = nil; void(^testBlock)() = ^(){ TLog(@"blockObj - block",blockObj); }; obj = nil; testBlock(); TLog(@"blockObj",blockObj);
变量内存地址:0x7fff57eef9f0, 变量值:0x7ff86a55a160, 指向对象值:11111111111111, --> obj 变量内存地址:0x7ff86c918a88, 变量值:0x7ff86a55a160, 指向对象值:11111111111111, --> blockObj - block 变量内存地址:0x7ff86c918a88, 变量值:0x7ff86a55a160, 指向对象值:11111111111111, --> blockObj
当外部 obj 指向 nil 的时候,obj 理应被释放,但实际上 blockObj 依然强引用着 obj,obj 其实并没有被真正释放。因此使用 __block
并不能避免循环引用的问题。
但是我们可以通过手动释放 blockObj 的方式来释放 obj,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj ,如下这种形式
MyObject *obj = [[MyObject alloc]init]; obj.text = @"11111111111111"; TLog(@"obj",obj); __block MyObject *blockObj = obj; obj = nil; void(^testBlock)() = ^(){ TLog(@"blockObj - block",blockObj); blockObj = nil; }; obj = nil; testBlock(); TLog(@"blockObj",blockObj);
必须记住在 block 底部释放掉 block 变量,这其实跟 MRC 的形式有些类似了,不太适合 ARC这种形式既能保证在 block 内部能够访问到 obj,又可以避免循环引用的问题,但是这种方法也不是完美的,其存在下面几个问题
- 当在 block 外部修改了 blockObj 时,block 内部的值也会改变,反之在 block 内部修改 blockObj 在外部再使用时值也会改变。这就需要在写代码时注意这个特性可能会带来的一些隐患
__block
其实提升了变量的作用域,在 block 内外访问的都是同一个 blockObj 可能会造成一些隐患
总结!!!
__weak
本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong
的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
__block
本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是 __block
修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。
但是__block
有一点:这只是限制在ARC环境下。在非arc下,__block是可以避免引用循环的