__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属性是,销毁实例 ...
随机推荐
- STM32基于HAL库通过DMA读写SDIO
通过STM32CUBEMX生成DMA读写sdio的工程,再读写过程中总会卡死在DMA中断等待读写完成的while中,最终发现while等待的标志在SDIO的中断里置位的,而SDIO中断优先级如果小于或 ...
- 关于百度编辑器UEditor的一点说明
大家在使用的时候要特别注意editor_config.js中的“URL”这个参数 我的理解:1.这个参数是editor整个结构的总路径 2.首先要把这个路径配置好了.才能正常的显示, ...
- 解析大型.NET ERP系统 通用附件管理功能
大型系统具备一个通用的附件管理功能,对于单据中无法清晰表达的字段,用一个附件图片或附件文档表示是最好的方法了.比如物料清单附加一张CAD图纸,销售订单评审功能中附加客户的各种表格,通用附件功能对系统起 ...
- 检索COM类工厂中CLSID为{00024500-0000-0000-C000-000000000046}的组件时失败
具体解决方法如下: 1:在服务器上安装office的Excel软件: 2:在"开始"->"运行"中输入dcomcnfg.exe启动"组件服务&q ...
- Hadoop入门学习笔记---part4
紧接着<Hadoop入门学习笔记---part3>中的继续了解如何用java在程序中操作HDFS. 众所周知,对文件的操作无非是创建,查看,下载,删除.下面我们就开始应用java程序进行操 ...
- css随笔记
使用伪类写边框部分三角 右上角三角形 border-top:6px solid #c1ddf7 border-left:6px solid transparent 右下角三角形 border-bott ...
- 想着模仿京东微信首页呢,banner滚动搞定了,写到了一半了
接着上篇 微信小程序-阅读小程序demo写:http://www.cnblogs.com/muyixiaoguang/p/5917986.html 想着模仿京东首页呢,结果也没赶得及写完,轮播图让我搞 ...
- 灵活可扩展的工作流管理平台Airflow
1. 引言 Airflow是Airbnb开源的一个用Python写就的工作流管理平台(workflow management platform).在前一篇文章中,介绍了如何用Crontab管理数据流, ...
- 如果你也会C#,那不妨了解下F#(5):模块、与C#互相调用
F# 项目 在之前的几篇文章介绍的代码都在交互窗口(fsi.exe)里运行,但平常开发的软件程序可能含有大类类型和函数定义,代码不可能都在一个文件里.下面我们来看VS里提供的F#项目模板. F#项目模 ...
- 【QuickHit项目实例】
关于java面向对象QuickHit项目实例 Game类:用来得到随机出现的字符串(随机打印的字符串,然后玩家进行输入字符串) public class Game { private String s ...