写给喜欢用Block的朋友(ios Block)

做者:fengsh998安全

原文地址:http://blog.csdn.net/fengsh998/article/details/38090205微信

转载请注明出处app

若是以为文章对你有所帮助,请经过留言或关注微信公众账号fengsh998来支持我,谢谢!框架



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

主要基于两点进行演示:this

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

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


有一次,朋友问我当一个对象中的block块中的访问本身的属性会不会形成循环引用,我哈绰绰的就回了一句,不会。兄弟,看完这个,但愿你能理解我为何会说不会循环引用。别废话,演示开始。.net


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

头文件.h

[objc] view plaincopy在CODE上查看代码片派生到个人代码片

  1. //  

  2. //  BlockDemo.h  

  3. //  blockDemo  

  4. //  

  5. //  Created by apple on 14-7-24.  

  6. //  Copyright (c) 2014年 fengsh. All rights reserved.  

  7. /* 

  8.  -fno-objc-arc 

  9.   

  10.  因为Block是默认创建在栈上, 因此若是离开方法做用域, Block就会被丢弃, 

  11.  在非ARC状况下, 咱们要返回一个Block ,须要 [Block copy]; 

  12.   

  13.  在ARC下, 如下几种状况, Block会自动被从栈复制到堆: 

  14.   

  15.  1.被执行copy方法 

  16.  2.做为方法返回值 

  17.  3.将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时 

  18.  4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中传递的时候. 

  19.  */  

  20.   

  21. #import <Foundation/Foundation.h>  

  22.   

  23. @class  BlockDemo;  

  24.   

  25. typedef void(^executeFinishedBlock)(void);  

  26. typedef void(^executeFinishedBlockParam)(BlockDemo *);  

  27.   

  28. @interface  BlockDemo : NSObject  

  29. {  

  30.     executeFinishedBlock finishblock;  

  31.     executeFinishedBlockParam finishblockparam;  

  32. }  

  33.   

  34. /** 

  35.  *  执行结果 

  36.  */  

  37. @property (nonatomic,assign) NSInteger resultCode;  

  38.   

  39. /** 

  40.  *  每次调用都产生一个新对象 

  41.  * 

  42.  *  @return  

  43.  */  

  44. + (BlockDemo *)blockdemo;  

  45.   

  46. /** 

  47.  *  不带参数的block 

  48.  * 

  49.  *  @param block 

  50.  */  

  51. - (void)setExecuteFinished:(executeFinishedBlock)block;  

  52.   

  53. /** 

  54.  *  带参数的block 

  55.  * 

  56.  *  @param block 

  57.  */  

  58. - (void)setExecuteFinishedParam:(executeFinishedBlockParam)block;  

  59.   

  60. - (void)executeTest;  

  61.   

  62.   

  63. @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,因此朋友一问起,我就说不会循环引用了,由于压根他碰到的就是前面讲述的哪一种访问方式,而我回答的是个人这种使用方式。正由于口头描述,与实际回复真是差之千里。。。哈哈。为了验证我朋友的这个,我特地写了个这篇文章,但愿对你们有所帮助。最后,谢谢你们花时间阅读。

相关文章
相关标签/搜索