__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-
变量内存地址:0x7fff5d7b2d10, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj-
变量内存地址:0x7fcf78f0f520, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj - block
变量内存地址:0x7fff5d7b2bb8, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> strongObj - block
变量内存地址:0x7fff5d7b2d10, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj-
变量内存地址:0x7fcf78f0f520, 变量值:0x0, 指向对象值:(null), --> weakObj - block
变量内存地址:0x7fff5d7b2bb8, 变量值:0x0, 指向对象值:(null), --> strongObj - block
变量内存地址:0x7fff5d7b2d10, 变量值:0x0, 指向对象值:(null), --> weakObj-
如果你看过 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, ), ^{
__strong MyObject *strongObj = weakObj;
TLog(@"weakObj - block", weakObj);
TLog(@"strongObj - block", strongObj); sleep(); TLog(@"weakObj - block", weakObj);
TLog(@"strongObj - block", strongObj);
});
NSLog(@"------ sleep 1s");
sleep();
obj = nil;
TLog(@"weakObj-1", weakObj);
NSLog(@"------ sleep 5s");
sleep();
TLog(@"weakObj-2", weakObj);
变量内存地址:0x7fff58e2ad18, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> obj
变量内存地址:0x7fff58e2ad10, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj-
变量内存地址:0x7fa2b1e80710, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj - block
变量内存地址:0x700000093de8, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> strongObj - block
------ sleep 1s
变量内存地址:0x7fff58e2ad10, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj-
------ sleep 5s
变量内存地址:0x7fa2b1e80710, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj - block
变量内存地址:0x700000093de8, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> strongObj - block
变量内存地址:0x7fff58e2ad10, 变量值:0x0, 指向对象值:(null), --> weakObj-
代码中使用 sleep 来保证代码执行的先后顺序。
从结果中我们可以看到,只要 block 部分执行了,即使我们中途释放了 obj,block 内部依然会继续强引用它。对比上面代码,也就是说 block 内部的 __strong 会在执行期间进行强引用操作,保证在 block 内部 strongObj 始终是可用的。这种写法非常巧妙,既避免了循环引用的问题,又可以在 block 内部持有该变量。
综合两部分代码,我们平时在使用时,常常先判断 strongObj 是否为空,然后再执行后续代码,如下方式:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
__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-, --> obj
变量内存地址:0x7fff5021a9e8, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-, --> blockObj -
<__NSMallocBlock__: 0x7ff6b48d8c20>
变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-, --> blockObj -
变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-, --> blockObj - block
变量内存地址:0x7fff5021a7f8, 变量值:0x7ff6b48d9960, 指向对象值:my-object-, --> obj2
变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d9960, 指向对象值:my-object-, --> blockObj - block
变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d9960, 指向对象值:my-object-, --> blockObj -
可以看到在 block 声明前后 blockObj 的内存地址是有所变化的,这涉及到 block 对外部变量的内存管理问题。
下面来看看 __block
能不能避免循环引用的问题
MyObject *obj = [[MyObject alloc]init];
obj.text = @"";
TLog(@"obj",obj); __block MyObject *blockObj = obj;
obj = nil;
void(^testBlock)() = ^(){
TLog(@"blockObj - block",blockObj);
};
obj = nil;
testBlock();
TLog(@"blockObj",blockObj);
变量内存地址:0x7fff57eef9f0, 变量值:0x7ff86a55a160, 指向对象值:, --> obj
变量内存地址:0x7ff86c918a88, 变量值:0x7ff86a55a160, 指向对象值:, --> blockObj - block
变量内存地址:0x7ff86c918a88, 变量值:0x7ff86a55a160, 指向对象值:, --> blockObj
当外部 obj 指向 nil 的时候,obj 理应被释放,但实际上 blockObj 依然强引用着 obj,obj 其实并没有被真正释放。因此使用 __block
并不能避免循环引用的问题。
但是我们可以通过手动释放 blockObj 的方式来释放 obj,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj ,如下这种形式
MyObject *obj = [[MyObject alloc]init];
obj.text = @"";
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是可以避免引用循环的
__weak与__block区别,深层理解两者区别的更多相关文章
- __weak 和 __block 区别
Blocks理解: Blocks可以访问局部变量,但是不能修改 如果修改局部变量,需要加__block __block int multiplier = 7; int (^myBlock)(int) ...
- 关于DDL、DML和DCL的区别与理解
2017年5月31日,天气阴.近期事情颇多,心情比较沉重. 端午刚过,早上上课,很多同学还处在端午的疲惫状态中没有回过神来,当然我也不例外.端午奔波三天,加上毕设的事情,可以说身心俱疲.状态不佳,整理 ...
- Cookies和Session的区别和理解
Cookies和Session的区别和理解 cookie机制 Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器.IETF RFC 2965 HTTP State Man ...
- CSS3中:nth-child和:nth-of-type的区别深入理解。 关于:nth-child和:nth-of-type的区别之前一直没太注意,经深入理解才发现里面其实暗藏玄机
关于:nth-child和:nth-of-type的区别之前一直没太注意.最近打算深入了解一些CSS3,才发现里面其实暗藏玄机. :nth-child可以选择父元素下的字元素,:nth-of-type ...
- CSS3中:nth-child和:nth-of-type的区别深入理解
关于:nth-child和:nth-of-type的区别之前一直没太注意.最近打算深入了解一些CSS3,才发现里面其实暗藏玄机. :nth-child可以选择父元素下的字元素,:nth-of-type ...
- 对于src路径问题,深层理解的实践。且对于输出流write()两个方法的源码阅读。
根据昨天的总结,可深层理解图片中src的路径.所以今天实现了一个想法.就是路径写入的是Controller,然后自动去本地找. 其实就是将电脑的本地图片 显示出来.通过输出流的方式. 代码如下: @R ...
- What the difference between __weak and __block reference?
近日遇到一个非常细的知识点,关于block的循环引用问题.相比非常多人都遇到了.也能顺利攻克了,至于block方面的技术文章.那就很多其它了.这里不再赘述,可是有这样一个问题: What the di ...
- HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别(转)
HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别 文章来源:http://www.cnblogs.com/beatIteWeNerverGiveU ...
- jQuery的datatable的destroy属性,和$("#test").dataTable().fnDestroy();区别,两者的区别
jQuery的datatable的destroy属性,和$("#test").dataTable().fnDestroy();区别,两者的区别. 1 destroy属性是,销毁实例 ...
随机推荐
- PHP安全之Web攻击
一.SQL注入攻击(SQL Injection) 攻击者把SQL命令插入到Web表单的输入域或页面请求的字符串,欺骗服务器执行恶意的SQL命令.在某些表单中,用户输入的内容直接用来构造(或者影响)动态 ...
- Python 小白的新手教程(一)
本文是 python 入门级别的基础知识,包括数据类型和变量.输入输出.字符串和编码.list tuple dict set .条件判断.循环.函数.切片 迭代 列表生成器 生成器 迭代器等. 参考课 ...
- SSIS 属性:ExecValueVariable
有些Task组件执行完成之后,会产生输出结果,称作Execution Value,例如,Execute SQL Task在执行完成之后,会返回受影响的数据行数.Task组件的Execution Val ...
- lua解析赋值类型代码的过程
我们来看看lua vm在解析下面源码并生成bytecode时的整个过程: foo = "bar" local a, b = "a", "b" ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(43)-工作流设计-字段分类设计
系列目录 建立好42节的表之后,每个字段英文表示都是有意义的说明.先建立,就知道表的关系和用处了,当然,我的设计只是一个参考,你可能有很多改进的地方. 我们的工作流具体细节流程是这样的: 最终我们的模 ...
- MySQL笔记---视图,存储过程, 触发器的使用入门
大二学数据库的时候,只是隐约听到老师提起过视图啊,存储过程啊,触发器啊什么的,但只是淡淡的记住了名字,后来自己做些小项目,小程序,也没有用上过,都只是简单的建表,关联表之类的,导致我对这些东西的理解只 ...
- ASP.NET Core 中文文档 第三章 原理(3)静态文件处理
原文:Working with Static Files 作者:Rick Anderson 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay).孟帅洋(书缘) 静态文 ...
- 大话keepalive
大话keepalive 我们说到keepalive的时候,需要先明确一点,这个keepalive说的是tcp的还是http的. tcp的keepalive是侧重在保持客户端和服务端的连接,一方会不定期 ...
- [未完成]scikit-learn一般实例之九:用于随机投影嵌入的Johnson–Lindenstrauss lemma边界
Johnson–Lindenstrauss 引理表明任何高维数据集均可以被随机投影到一个较低维度的欧氏空间,同时可以控制pairwise距离的失真. 理论边界 由一个随机投影P所引入的失真是确定的,这 ...
- Ionic2系列——Ionic 2 Guide 官方文档中文版
最近一直没更新博客,业余时间都在翻译Ionic2的文档.之前本来是想写一个入门,后来觉得干脆把官方文档翻译一下算了,因为官方文档就是最好的入门教程.后来越翻译越觉得这个事情确实比较费精力,不知道什么时 ...