喜欢用Block的值得注意-Block的Retain Cycle的解决方法

本文不讲block如何声明及使用,只讲block在使用过程中暂时遇到及带来的隐性危险。

主要基于两点进行演示:

1.block 的循环引用(retain cycle)

2.去除block产生的告警时,需注意问题。


有一次,朋友问我当一个对象中的block块中的访问自己的属性会不会造成循环引用,我哈绰绰的就回了一句,不会。兄弟,看完这个,希望你能理解我为什么会说不会循环引用。别废话,演示开始。


下面是我专们写了一个类来演示:

头文件.h

[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. //  
  2. //  BlockDemo.h  
  3. //  blockDemo  
  4. /* 
  5.  -fno-objc-arc 
  6.   
  7.  由于Block是默认建立在栈上, 所以如果离开方法作用域, Block就会被丢弃, 
  8.  在非ARC情况下, 我们要返回一个Block ,需要 [Block copy]; 
  9.   
  10.  在ARC下, 以下几种情况, Block会自动被从栈复制到堆: 
  11.   
  12.  1.被执行copy方法 
  13.  2.作为方法返回值 
  14.  3.将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时 
  15.  4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中传递的时候. 
  16.  */  
  17.   
  18. #import <Foundation/Foundation.h>  
  19.   
  20. @class BlockDemo;  
  21.   
  22. typedef void(^executeFinishedBlock)(void);  
  23. typedef void(^executeFinishedBlockParam)(BlockDemo *);  
  24.   
  25. @interface BlockDemo : NSObject  
  26. {  
  27.     executeFinishedBlock finishblock;  
  28.     executeFinishedBlockParam finishblockparam;  
  29. }  
  30.   
  31. /** 
  32.  *  执行结果 
  33.  */  
  34. @property (nonatomic,assign) NSInteger resultCode;  
  35.   
  36. /** 
  37.  *  每次调用都产生一个新对象 
  38.  * 
  39.  *  @return 
  40.  */  
  41. + (BlockDemo *)blockdemo;  
  42.   
  43. /** 
  44.  *  不带参数的block 
  45.  * 
  46.  *  @param block 
  47.  */  
  48. - (void)setExecuteFinished:(executeFinishedBlock)block;  
  49.   
  50. /** 
  51.  *  带参数的block 
  52.  * 
  53.  *  @param block 
  54.  */  
  55. - (void)setExecuteFinishedParam:(executeFinishedBlockParam)block;  
  56.   
  57. - (void)executeTest;  
  58.   
  59.   
  60. @end  

实现文件

[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. //  
  2. //  BlockDemo.m  
  3. //  blockDemo  
  4. //  
  5. //  Created by apple on 14-7-24.  
  6. //  Copyright (c) 2014年 fengsh. All rights reserved.  
  7. //  
  8.   
  9. #if __has_feature(objc_arc) && __clang_major__ >= 3  
  10.     #define OBJC_ARC_ENABLED 1  
  11. #endif // __has_feature(objc_arc)  
  12.   
  13. #if OBJC_ARC_ENABLED  
  14.     #define OBJC_RETAIN(object)         (object)  
  15.     #define OBJC_COPY(object)           (object)  
  16.     #define OBJC_RELEASE(object)        object = nil  
  17.     #define OBJC_AUTORELEASE(object)    (object)  
  18. #else  
  19.     #define OBJC_RETAIN(object)           [object retain]  
  20.     #define OBJC_COPY(object)             [object copy]  
  21.     #define OBJC_RELEASE(object)          [object release], object = nil  
  22.     #define OBJC_AUTORELEASE(object)      [object autorelease]  
  23. #endif  
  24.   
  25. #import "BlockDemo.h"  
  26.   
  27. @implementation BlockDemo  
  28.   
  29.   
  30. + (BlockDemo *)blockdemo  
  31. {  
  32.     return OBJC_AUTORELEASE([[BlockDemo alloc]init]);  
  33. }  
  34.   
  35. - (id)init  
  36. {  
  37.     self = [super init];  
  38.     if (self) {  
  39.         NSLog(@"Object Constructor!");  
  40.     }  
  41.     return self;  
  42. }  
  43.   
  44. - (void)dealloc  
  45. {  
  46.     NSLog(@"Object Destoryed!");  
  47. #if !__has_feature(objc_arc)  
  48.     [super dealloc];  
  49. #endif  
  50. }  
  51.   
  52. - (void)setExecuteFinished:(executeFinishedBlock)block  
  53. {  
  54.     OBJC_RELEASE(finishblock);  
  55.     finishblock = OBJC_COPY(block); //在非ARC下这里不能使用retain  
  56. }  
  57.   
  58. - (void)setExecuteFinishedParam:(executeFinishedBlockParam)block  
  59. {  
  60.     OBJC_RELEASE(finishblockparam);  
  61.     finishblockparam = OBJC_COPY(block); //在非ARC下这里不能使用retain  
  62. }  
  63.   
  64. - (void)executeTest  
  65. {  
  66.     [self performSelector:@selector(executeCallBack) withObject:nil afterDelay:5];  
  67. }  
  68.   
  69. - (void)executeCallBack  
  70. {  
  71.     _resultCode = 200;  
  72.       
  73.     if (finishblock)  
  74.     {  
  75.         finishblock();  
  76.     }  
  77.       
  78.     if (finishblockparam)  
  79.     {  
  80.         finishblockparam(self);  
  81.     }  
  82. }  
  83.   
  84. @end  

上面是因为考虑到在ARC 和非ARC中进行编译演示,所以我特意加了ARC预编译判断。主要是方便不要改动太多的代码来给大家演示。


在非ARC环境下


执行下在语句的测试:

[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. - (IBAction)onTest:(id)sender  
  2. {  
  3.     BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];  
  4.       
  5.     [demo setExecuteFinished:^{  
  6.         if (demo.resultCode == 200) {  
  7.             NSLog(@"call back ok.");  
  8.         }  
  9.     }];  
  10.       
  11.     [demo executeTest];  
  12.        
  13. }  

输出结果:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. 2014-07-24 19:08:04.852 blockDemo[25104:60b] Object Constructor!  
  2. 2014-07-24 19:08:09.854 blockDemo[25104:60b] call back ok.  

很显然。尽管demo 是局部变量,并autorelease但可以看出自然至终并没有得到释放,这是因为block中使用了 block内进行访问了自身的resultCode属性。相信很多朋友也都会解决这种循环引用问题。就是在变量前面加个__block,就像这样。

[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. __block BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];  
在非ARC下,只虽一个__block关键词就可以。相对还是简单的。

好下面再来看一下在ARC模式下的block循环引用又是怎么样的。

在ARC模式下

执行下面语句:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. - (IBAction)onTest:(id)sender  
  2. {  
  3.     BlockDemo *demo = [[BlockDemo alloc]init];  
  4.     [demo setExecuteFinished:^{  
  5.         if (demo.resultCode == 200) {  
  6.             NSLog(@"call back ok.");  
  7.         }  
  8.     }];  
  9.       
  10.     [demo executeTest];  
  11.        
  12. }  

执行输出结果:

[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. 2014-07-24 19:20:33.997 blockDemo[25215:60b] Object Constructor!  
  2. 2014-07-24 19:20:39.000 blockDemo[25215:60b] call back ok.  
同样会被引入循环。

相信看到这里的人,大多都要喷了,这哪个不知道呀,还知道怎么解决呢,非ARC中加了个__block,当然的在ARC中加一个__weak就搞定了。嗯,确实是这样,但别急,接着往下看,绝对有收获。在这里先自己默认想一下,你是如何加这个__weak的。

对于第一个问是点block 的循环引用(retain cycle)到这里暂告结束。下面讲第二点。因为block告警在非ARC 中暂未发现因写法引入(如果你知道,麻烦告诉我怎么弄产生告警,我好研究一下。)

下面讲在ARC模式下去除因写法产生的告警时需要注意的问题。

像上面的写法其实在ARC中会产生(Capturing 'demo' strongly in this block is likely to lead to a retain cycle)告警。如下图:


在ARC中,编译器智能化了,直接提示这样写会产生循环引用。因此很多爱去除告警的朋友就会想法去掉,好,咱再来看去掉时需注意的问题。

情况一:

[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. - (IBAction)onTest:(id)sender  
  2. {  
  3.     __weak BlockDemo *demo = [[BlockDemo alloc]init];  
  4.     [demo setExecuteFinished:^{  
  5.         if (demo.resultCode == 200) {  
  6.             NSLog(@"call back ok.");  
  7.         }  
  8.     }];  
  9.     [demo executeTest];  
  10. }  
直接在前面加一个__weak,但这样真的没有告警了吗?如果有,哪么恭喜欢你,说明编译器还帮你大忙。见下图



这时还会告警,说这是一个WEAK变量,就马上会被release。因此就不会执行block中的内容。大家可以运行一下看

输出结果为:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. 2014-07-24 19:38:02.453 blockDemo[25305:60b] Object Constructor!  
  2. 2014-07-24 19:38:02.454 blockDemo[25305:60b] Object Destoryed!  
很显然,马上被release了,所以block 中的代码根本就不执行。

谢天谢地,幸好编译器提前告诉了我们有这个隐性危险。相信大家为解决告警,又会得到一个比较圆满的解决方案,见下:

[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. - (IBAction)onTest:(id)sender  
  2. {  
  3.     BlockDemo *demo = [[BlockDemo alloc]init];  
  4.       
  5.     __weak typeof(BlockDemo) *weakDemo = demo;  
  6.       
  7.     [demo setExecuteFinished:^{  
  8.         if (weakDemo.resultCode == 200) {  
  9.             NSLog(@"call back ok.");  
  10.         }  
  11.     }];  
  12.     [demo executeTest];  
  13. }  

这样写,即去除了告警又保证了block的运行。这才是我们最终想要的结果。
输出为:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. 2014-07-24 19:40:33.204 blockDemo[25328:60b] Object Constructor!  
  2. 2014-07-24 19:40:38.206 blockDemo[25328:60b] call back ok.  
  3. 2014-07-24 19:40:38.207 blockDemo[25328:60b] Object Destoryed!  

但大家别得意。有提示,相信大家都能处理,并得到个好的解决方法。哪么下面大来再来看一下这个写法,让你真心甘拜下风。。。。。

[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. - (IBAction)onTest:(id)sender  
  2. {  
  3.     __weak BlockDemo *demo = [BlockDemo blockdemo]; //这里才是重点,前面是[[BlockDemo alloc]init];会有告警。  
  4.       
  5.     [demo setExecuteFinished:^{  
  6.         if (demo.resultCode == 200) {  
  7.             NSLog(@"call back ok.");  
  8.         }  
  9.     }];  
  10.     [demo executeTest];  
  11. }  


其实只是把init放到了类方法中进行书写而已,但会有什么不同。
[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. + (BlockDemo *)blockdemo  
  2. {  
  3.     return OBJC_AUTORELEASE([[BlockDemo alloc]init]);  
  4. }  
不同点见下图:真心看不到作何告警,是不是。但这存在什么风险,风险就是运行的时候,block根本就没有run。因为对象早就释放了。


直接输出:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. 2014-07-24 19:47:53.033 blockDemo[25395:60b] Object Constructor!  
  2. 2014-07-24 19:47:53.035 blockDemo[25395:60b] Object Destoryed!  

因此,写这个主要用来告戒一些喜欢用BLOCK但又想当然的朋友,有一些朋友喜欢去除告警,但只是盲目的加上__weak 或__block关键语,往往可能存在一些重大的安全隐患。就像演示中block根本不走。如果到了发布时,为了去告警而这样简单的处理了,并没有进行测试就打包。哪么将死得很惨。。。。。


好,到了尾声,来说说为什么朋友问我block会不会引行死循环,我说不会的理由。

见码:

[objc] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. - (IBAction)onTest:(id)sender  
  2. {  
  3.     BlockDemo *demo = [BlockDemo blockdemo];//[[BlockDemo alloc]init];  
  4.       
  5.     [demo setExecuteFinishedParam:^(BlockDemo * ademo) {  
  6.         if (ademo.resultCode == 200) {  
  7.             NSLog(@"call back ok.");  
  8.         }  
  9.     }];  
  10.       
  11.     [demo executeTest];  
  12. }  

不管是在外面init,还是在里面,且没有加__block 及__weak。为什么,因为我个人常常在使用自己写的block时,如果是回调,比较喜欢把自身当作参数传到block中。这样期实是编译器给我们做了弱引用。因此不会产生循环引用。

由于我一直都这样写block,所以朋友一问起,我就说不会循环引用了,因为压根他碰到的就是前面讲述的哪种访问方式,而我回答的是我的这种使用方式。正因为口头描述,与实际回复真是差之千里。。。哈哈。为了验证我朋友的这个,我特意写了个这篇文章,希望对大家有所帮助。最后,谢谢大家花时间阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/247929.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PyQt5】QT designer + eclipse 集成开发

【写在前面的话】 考虑将pyqt5的界面开发qt designer 集成在eclipse中&#xff0c;并且&#xff0c;不利用cmd命令行进行转换。 【工具】 1、pyqt5 2、qt designer 3、eclipse pydy 【步骤】 1、首先配置Qt designer。 菜单 run-->external Tools-->External tools confi…

iOS UIlabel文字排版(改变字间距行间距)分类

在iOS开发中经常会用到UIlabel来展示一些文字性的内容&#xff0c;但是默认的文字排版会觉得有些挤&#xff0c;为了更美观也更易于阅读我们可以通过某些方法将UIlabel的行间距和字间距按照需要调节。 比如一个Label的默认间距效果是这样&#xff1a; 然后用一个封装起来的Cat…

iOS 富文本风格NSMutableParagraphStyle、定制UITextView插入图片和定制复制

问题一 开发过程中&#xff0c;经常会遇到动态计算行高的问题&#xff0c; - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(nullableNSDictionary<NSString *, id> *)attributes context:(nullable NSStringDrawingC…

线程模块

信号量 from threading import Semaphore,Thread import timedef func(a,b):time.sleep(1)sem.acquire()print(ab)sem.release()sem Semaphore(4) for i in range(10):t Thread(targetfunc,args(i,i5))t.start() 信号量事件 # 事件被创建的时候&#xff0c;默认为False状态 #…

React中级学习(第一天)

Props深入 children 作用 : 获取组件标签的 子节点获取方式 : this.props.children <App>此处的内容&#xff0c;就是组件的 children&#xff0c;将来通过组件的 props.children 就可以获取到这些子节点了 </App>props 校验 作用&#xff1a;规定组件props的类…

Elasticsearch集成ik分词器

1、插件地址https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.0.0/elasticsearch-analysis-ik-7.0.0.zip 2、找到对应版本的插件通过 http://192.168.1.8:9200查看ES的版本&#xff0c;找到对应的IK分词插件 下载与之对应的版本https://github.com/me…

iOS NSTextAttachment - 图文混排

苹果在iOS7中推出了一个新的类NSTextAttachment&#xff0c;它是做图文混排的利器&#xff0c;本文就是用这个类&#xff0c;只用50行代码实现文字与表情混排&#xff0c;当然也可以实现段落中的图文混排。 首先说一下文字和表情的混排&#xff1a; 先来做点儿准备工作&#…

iOS 自定义Cell按钮的点击代理事件

在实际开发工作中&#xff0c;我们经常会在自定义的Cell中布局一些按钮&#xff0c;并且很多时候我们会在点击这个按钮的时候使我们的UItableviewController跳转到下一界面&#xff0c;有的可能还要传值。那么如何使我们的控制器能够获知我们按下了cell的按钮呢&#xff1f;毫无…

js - (初中级)常见笔试面试题

1.用 js 实现一个深拷贝 2.用 js 写一个数组去重 3. 用 js 对字符串进行反转 4. 用 js 请求范围内的质数个数 5.用 js 求数组中出现最多的数及其出现次数

iOS 支付宝SDK接入详解

一&#xff0c;在支付宝开放平台下载支付宝SDK&#xff08;https://openhome.alipay.com/platform/document.htm#down&#xff09; https://doc.open.alipay.com/doc2/detail.htm?spma219a.7629140.0.0.HpDuWo&treeId54&articleId104509&docType1 二&#xff0c;添…

面试基本知识点

文章目录面-什么是SEO面 - cookie / localstorage / sessionstorage的区别面 - promise面试题面 - 柯里化函数面 - 函数节流面 - 函数防抖HTML / CSS 知识点1、讲讲盒模型&#xff08;蚂蚁金服 2019.03 招行信用卡 2019.04 美团 作业帮&#xff09;2、根据盒模型解释边距重叠&a…

Redis 热点key

压测报redis 热点问题 热点问题概述 产生原因 热点问题产生的原因大致有以下两种&#xff1a; 用户消费的数据远大于生产的数据&#xff08;热卖商品、热点新闻、热点评论、明星直播&#xff09;。 在日常工作生活中一些突发的的事件&#xff0c;例如&#xff1a;双十一期间某些…

移动IM开发那些事:技术选型和常见问题

最近在做一个iOS IM SDK&#xff0c;在内部试用的阶段&#xff0c;不断有兄弟部门或者合作伙伴过来问各种技术细节&#xff0c;所以统一写一篇文章记录&#xff0c;统一介绍下一个IM APP的方方面面&#xff0c;包括技术选型(包括通讯方式,网络连接方式,协议选择)和常见问题。 …

webstrom打开通过顶部浏览器打开网页,被跳转到默认主页

重新开始工作啦&#xff0c;希望以后认真一点&#xff0c;并把遇到的问题都记录下来&#xff0c;虽然是小小白&#xff0c;但能无意间帮助到别人就更开心了呀 通过webstrom打开本地的文件时&#xff0c;发现跳转到了默认主页上&#xff0c;吐槽下&#xff0c;有些主页真的超级流…

mockjs(接口服务代理)

mock官网&#xff1a;http://mockjs.com/ 一、搭建一个练习项目 1.利用vue的项目脚手架进行搭建 命令&#xff1a; vue create mock-demo 截图&#xff1a; 2.安装相关的依赖 命令&#xff1a; #使用 axios 发送 ajax npm install axios --save #使用 mock.js 产生随机数据…

MD5算法原理

MD5&#xff08;单向散列算法&#xff09; 的全称是Message-Digest Algorithm 5&#xff08;信息-摘要算法&#xff09;&#xff0c;经MD2、MD3和MD4发展而来。MD5算法的使用不需要支付任何版权费用。MD5功能&#xff1a;输入任意长度的信息&#xff0c;经过处理&#xff0c;输…

python第五次作业——陈灵院

习题1&#xff1a;读入文件pmi_days.csv&#xff0c;完成以下操作&#xff1a;1.统计质量等级对应的天数&#xff0c;例如&#xff1a;优&#xff1a;5天良&#xff1a;3天中度污染&#xff1a;2天2.找出PMI2.5的最大值和最小值&#xff0c;分别指出是哪一天。 import csv impo…

iOS 二叉树相关算法实现

什么是二叉树&#xff1f; 在计算机科学中&#xff0c;二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”&#xff0c;左子树和右子树同时也是二叉树。二叉树的子树有左右之分&#xff0c;并且次序不能任意颠倒。二叉树是递归定义的&#xff0c;所…

vux 组件库首次使用安装

1、首先通过脚手架新建一个项目&#xff0c;过程略。 创建完项目后&#xff0c;在项目里安装vux&#xff0c; 通过命令 npm install vux --save 安装 2、安装vux-loader&#xff0c; 通过命令 npm install vux-loader --save-dev 安装&#xff08;vux文档没说明&#xff09; 3、…

爬取豆瓣top250

#xpath #第一种方法 可在开发者工具中找到标签&#xff0c;右键copy xpath&#xff0c;有时需去掉tbody标签 #第二种方法 简单学习xpath&#xff0c;自己书写&#xff0c;掌握基本语法即可&#xff0c;简单的层级关系#先将csv文件以记事本打开&#xff0c;更改编码为ASNI&…