喜欢用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,一经查实,立即删除!

相关文章

小程序基础 (三)

5. 使用 slot 使用单个slot // 页面 <Test><view>自定义内容</view> </Test>// 组件 <view><view>前面的内容</view><slot></slot><view>后面的内容</view> </view>使用多个slot - 具名 // 页面 &…

【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…

MySQL查询之聚合查询

为了快速得到统计数据&#xff0c;提供了5个聚合函数&#xff1a; count(*)表示计算总行数&#xff0c;括号中写星与列名&#xff0c;结果是相同的 查询学生总数 select count(*) from students; max(列)表示求此列的最大值 查询女生的编号最大值 select max(id) from students…

React基础学习(第一天)

React 概述 : React 是一个用于 构建用户界面 的 JavaScript 库因为框架是有一整套解决方案的&#xff0c;React就是纯粹写UI组件的 没有什么异步处理机制、模块化、表单验证这些。React和react-router, react-redux结合起来才叫框架&#xff0c;而React本身只是充当一个前端…

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

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

day24 01 初识继承

day24 01 初识继承 面向对象的三大特性&#xff1a;继承&#xff0c;多态&#xff0c;封装 一、继承的概念 继承&#xff1a;是一种创建新类的方式&#xff0c;新建的类可以继承一个或者多个父类&#xff0c;父类又可称基类或超类&#xff0c;新建的类称为派生类或者子类 class…

React基础学习(第二天)

虚拟DOM JSX 涉及到 虚拟DOM ,简单聊一下 定时器渲染问题 // 方法 function render() {//2. 创建react对象let el (<div><h3>时间更新</h3><p>{ new Date().toLocaleTimeString()}</p></div>)//3. 渲染ReactDOM.render(el, document.g…

iOS 去除字符串中的空格或多余空格(适合英文单词)

NSString -stringByTrimmingCharactersInSet: 是个你需要牢牢记住的方法。它经常会传入 NSCharacterSet whitespaceCharacterSet 或 whitespaceAndNewlineCharacterSet 来删除输入字符串的头尾的空白符号。 需要重点注意的是&#xff0c;这个方法 仅仅 去除了 开头 和 结尾 的…

华为交换机在Telnet登录下自动显示接口信息

因为用console连接交换机&#xff0c;默认是自动显示接口信息的&#xff0c;比如down掉一个接口后&#xff0c;会自动弹出接口被down掉的信息&#xff0c;但是在telnet连接下&#xff0c;默认是不显示这些信息的&#xff0c;需要开启后才可显示。 1、首先开启info-center(默认是…

React基础学习(第三天)

条件渲染 1. if / else render () {if (this.state.isLoading) { // 正在加载中return <h1>Loading...</h1>}return <div>这就是我们想要的内容</div>} // 钩子函数 五秒钟之后 修改状态值componentDidMount () { setTimeout(() > {this.setState(…

componentsJoinedByString 和 componentsSeparatedByString 的方法的区别

将string字符串转换为array数组 NSArray *array [Str componentsSeparatedByString:","]; &#xff1d;&#xff1d;反向方法 将array数组转换为string字符串 NSString *tempString [mutableArray componentsJoinedByString:","];--,是分隔符 可不加分隔…

java中的各种数据类型在内存中存储的方式

转载别人的附上链接&#xff1a;https://blog.csdn.net/zj15527620802/article/details/80622314 1.java是如何管理内存的 java的内存管理就是对象的分配和释放问题。&#xff08;其中包括两部分&#xff09; 分配&#xff1a;内存的分配是由程序完成的&#xff0c;程序员需要通…

vscode的 jsonp 配置文件

{ // 工具-字体大小 “editor.fontSize”: 15, // 工具-tab缩进 “editor.tabSize”: 2, // 工具-在视区宽度换行 “editor.wordWrap”: “on”, // 工具-缩放 “window.zoomLevel”: 1, // 工具-编写tab识别语言格式 “emmet.includeLanguages”: { “vue-html”: “html”, “…

NSString拼接字符串和NSPredicate详解

NSString* string; // 结果字符串 02 NSString* string1, string2; //已存在的字符串&#xff0c;需要将string1和string2连接起来 03 04 //方法1. 05 string [[NSString alloc]initWithFormat:"%,%", string1, string2 ]; 06 07 //方法2. 08 string [string1 …

线程模块

信号量 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的类…

iOS 正则表达式判断纯数字以及匹配11位手机号码

1用正则表达式 //是否是纯数字(BOOL)isNumText:(NSString *)str{NSString * regex "(/^[0-9]*$/)";NSPredicate * pred [NSPredicate predicateWithFormat:"SELF MATCHES %", regex];BOOL isMatch [pred evaluateWithObject:st…

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…

React中级学习(第二天)

JSX 语法的转化过程 (了解) 演示 : babel中文网试一试 let h1 JSX 仅仅是createElement() 方法的语法糖 (简化语法)JSX 语法 被 babel/preset-react 插件编译为 createElement() 方法React 元素&#xff1a;是一个对象&#xff0c;用来描述你希望在屏幕上看到的内容React 元素…