__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属性是,销毁实例 ...
随机推荐
- ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程
从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...
- 安卓Design包之超强控件CoordinatorLayout与SnackBar的简单使用
在前面的Design中,学习使用了TabLayout,NavigationView与DrawerLayout实现的神奇效果,今天就带来本次Design包中我认为最有意义的控件CoordinatorLa ...
- Android ScrollView监听滑动到顶部和底部的两种方式(你可能不知道的细节)
Android ScrollView监听滑动到顶部和底部,虽然网上很多资料都有说,但是不全,而且有些细节没说清楚 使用场景: 1. 做一些复杂动画的时候,需要动态判断当前的ScrollView是否滚动 ...
- HTML基本元素(三)
1.HTML特殊字符 一些字符在HTML中拥有特殊的含义,比如小于号(<)和大于号(>)用于定义HTML标签.如果我们希望浏览器正确地显示这些字符,我们必须在HTML源码中插入字符实体. ...
- Java进击C#——应用开发之Asp.net MVC
本章简言 上一章笔者讲到关于Asp.NET的知识点.了解Asp.NET基本的知识点之后,我们在来学习关于C#的MVC框架就简单多了.显然本章就是来介绍一下关于Asp.NET MVC.对于MVC的思想笔 ...
- 使用Autolayout实现UITableView的Cell动态布局和高度动态改变
本文翻译自:stackoverflow 有人在stackoverflow上问了一个问题: 1 如何在UITableViewCell中使用Autolayout来实现Cell的内容和子视图自动计算行高,并 ...
- 『.NET Core CLI工具文档』(八)dotnet-restore
说明:本文是个人翻译文章,由于个人水平有限,有不对的地方请大家帮忙更正. 原文:dotnet-restore 翻译:dotnet-restore 名称 dotnet-restore - 还原一个项目的 ...
- java JSP(原创新手可进)
一. 同等编程方式jsp与asp.net的不同 app需要做一个简单网站,和几个用户推广链接,所以涉及到web这块开发,原本昨天想直接使用asp.net来做,但是之后放弃了这个想法,因为数据访问接口都 ...
- 让linux开机默认开启小键盘
linux默认开机不开启数字键盘numberlock,每次输入开机密码还得劳烦自己去点亮指示灯,让此灯开机自动点亮,需要一个软件才行,就是numlockx了,可以通过yum安装:yuminstall ...
- Java - 网络编程
Java的网络编程学习,关于计算机基础的学习参考:计算机网络基础学习 - sqh. 参考: