写给喜欢用Block的朋友(ios Block)
-
作者:fengsh998原文地址:http://blog.csdn.net/fengsh998/article/details/38090205转载请注明出处如果觉得文章对你有所帮助,请通过留言或关注微信公众帐号fengsh998来支持我,谢谢!
http://blog.csdn.net/fengsh998/article/details/38090205
本文不讲block如何声明及使用,只讲block在使用过程中暂时遇到及带来的隐性危险。
主要基于两点进行演示:
1.block 的循环引用(retain cycle)
2.去除block产生的告警时,需注意问题。
有一次,朋友问我当一个对象中的block块中的访问自己的属性会不会造成循环引用,我哈绰绰的就回了一句,不会。兄弟,看完这个,希望你能理解我为什么会说不会循环引用。别废话,演示开始。
下面是我专们写了一个类来演示:
头文件.h
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364//// BlockDemo.h// blockDemo//// Created by apple on 14-7-24.// Copyright (c) 2014年 fengsh. All rights reserved./*-fno-objc-arc由于Block是默认建立在栈上, 所以如果离开方法作用域, Block就会被丢弃,在非ARC情况下, 我们要返回一个Block ,需要 [Block copy];在ARC下, 以下几种情况, Block会自动被从栈复制到堆:1.被执行copy方法2.作为方法返回值3.将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中传递的时候.*/#import<foundation foundation.h="">@classBlockDemo;typedefvoid(^executeFinishedBlock)(void);typedefvoid(^executeFinishedBlockParam)(BlockDemo *);@interfaceBlockDemo : NSObject{executeFinishedBlock finishblock;executeFinishedBlockParam finishblockparam;}/*** 执行结果*/@property(nonatomic,assign) NSInteger resultCode;/*** 每次调用都产生一个新对象** @return*/+ (BlockDemo *)blockdemo;/*** 不带参数的block** @param block*/- (void)setExecuteFinished:(executeFinishedBlock)block;/*** 带参数的block** @param block*/- (void)setExecuteFinishedParam:(executeFinishedBlockParam)block;- (void)executeTest;@end</foundation>实现文件
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384//// BlockDemo.m// blockDemo//// Created by apple on 14-7-24.// Copyright (c) 2014年 fengsh. All rights reserved.//#if__has_feature(objc_arc) && __clang_major__ >=3#define OBJC_ARC_ENABLED1#endif// __has_feature(objc_arc)#ifOBJC_ARC_ENABLED#define OBJC_RETAIN(object) (object)#define OBJC_COPY(object) (object)#define OBJC_RELEASE(object) object = nil#define OBJC_AUTORELEASE(object) (object)#else#define OBJC_RETAIN(object) [object retain]#define OBJC_COPY(object) [object copy]#define OBJC_RELEASE(object) [object release], object = nil#define OBJC_AUTORELEASE(object) [object autorelease]#endif#import"BlockDemo.h"@implementationBlockDemo+ (BlockDemo *)blockdemo{returnOBJC_AUTORELEASE([[BlockDemo alloc]init]);}- (id)init{self = [superinit];if(self) {NSLog(@"Object Constructor!");}returnself;}- (void)dealloc{NSLog(@"Object Destoryed!");#if!__has_feature(objc_arc)[superdealloc];#endif}- (void)setExecuteFinished:(executeFinishedBlock)block{OBJC_RELEASE(finishblock);finishblock = OBJC_COPY(block);//在非ARC下这里不能使用retain}- (void)setExecuteFinishedParam:(executeFinishedBlockParam)block{OBJC_RELEASE(finishblockparam);finishblockparam = OBJC_COPY(block);//在非ARC下这里不能使用retain}- (void)executeTest{[self performSelector:@selector(executeCallBack) withObject:nil afterDelay:5];}- (void)executeCallBack{_resultCode =200;if(finishblock){finishblock();}if(finishblockparam){finishblockparam(self);}}@end上面是因为考虑到在ARC 和非ARC中进行编译演示,所以我特意加了ARC预编译判断。主要是方便不要改动太多的代码来给大家演示。
在非ARC环境下
执行下在语句的测试:
12345678910111213- (IBAction)onTest:(id)sender{BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];[demo setExecuteFinished:^{if(demo.resultCode ==200) {NSLog(@"call back ok.");}}];[demo executeTest];}输出结果:
122014-07-2419:08:04.852blockDemo[25104:60b] Object Constructor!2014-07-2419:08:09.854blockDemo[25104:60b] call back ok.很显然。尽管demo 是局部变量,并autorelease但可以看出自然至终并没有得到释放,这是因为block中使用了 block内进行访问了自身的resultCode属性。相信很多朋友也都会解决这种循环引用问题。就是在变量前面加个__block,就像这样。
1__block BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];在非ARC下,只虽一个__block关键词就可以。相对还是简单的。
好下面再来看一下在ARC模式下的block循环引用又是怎么样的。
在ARC模式下
执行下面语句:
123456789101112- (IBAction)onTest:(id)sender{BlockDemo *demo = [[BlockDemo alloc]init];[demo setExecuteFinished:^{if(demo.resultCode ==200) {NSLog(@"call back ok.");}}];[demo executeTest];}执行输出结果:
122014-07-2419:20:33.997blockDemo[25215:60b] Object Constructor!2014-07-2419:20:39.000blockDemo[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中,编译器智能化了,直接提示这样写会产生循环引用。因此很多爱去除告警的朋友就会想法去掉,好,咱再来看去掉时需注意的问题。
情况一:
12345678910- (IBAction)onTest:(id)sender{__weak BlockDemo *demo = [[BlockDemo alloc]init];[demo setExecuteFinished:^{if(demo.resultCode ==200) {NSLog(@"call back ok.");}}];[demo executeTest];}直接在前面加一个__weak,但这样真的没有告警了吗?如果有,哪么恭喜欢你,说明编译器还帮你大忙。见下图

这时还会告警,说这是一个WEAK变量,就马上会被release。因此就不会执行block中的内容。大家可以运行一下看
输出结果为:
122014-07-2419:38:02.453blockDemo[25305:60b] Object Constructor!2014-07-2419:38:02.454blockDemo[25305:60b] Object Destoryed!很显然,马上被release了,所以block 中的代码根本就不执行。
谢天谢地,幸好编译器提前告诉了我们有这个隐性危险。相信大家为解决告警,又会得到一个比较圆满的解决方案,见下:
12345678910111213- (IBAction)onTest:(id)sender{BlockDemo *demo = [[BlockDemo alloc]init];__weak typeof(BlockDemo) *weakDemo = demo;[demo setExecuteFinished:^{if(weakDemo.resultCode ==200) {NSLog(@"call back ok.");}}];[demo executeTest];}这样写,即去除了告警又保证了block的运行。这才是我们最终想要的结果。
输出为:1232014-07-2419:40:33.204blockDemo[25328:60b] Object Constructor!2014-07-2419:40:38.206blockDemo[25328:60b] call back ok.2014-07-2419:40:38.207blockDemo[25328:60b] Object Destoryed!但大家别得意。有提示,相信大家都能处理,并得到个好的解决方法。哪么下面大来再来看一下这个写法,让你真心甘拜下风。。。。。
1234567891011- (IBAction)onTest:(id)sender{__weak BlockDemo *demo = [BlockDemo blockdemo];//这里才是重点,前面是[[BlockDemo alloc]init];会有告警。[demo setExecuteFinished:^{if(demo.resultCode ==200) {NSLog(@"call back ok.");}}];[demo executeTest];}其实只是把init放到了类方法中进行书写而已,但会有什么不同。
1234+ (BlockDemo *)blockdemo{returnOBJC_AUTORELEASE([[BlockDemo alloc]init]);}不同点见下图:真心看不到作何告警,是不是。但这存在什么风险,风险就是运行的时候,block根本就没有run。因为对象早就释放了。

直接输出:122014-07-2419:47:53.033blockDemo[25395:60b] Object Constructor!2014-07-2419:47:53.035blockDemo[25395:60b] Object Destoryed!因此,写这个主要用来告戒一些喜欢用BLOCK但又想当然的朋友,有一些朋友喜欢去除告警,但只是盲目的加上__weak 或__block关键语,往往可能存在一些重大的安全隐患。就像演示中block根本不走。如果到了发布时,为了去告警而这样简单的处理了,并没有进行测试就打包。哪么将死得很惨。。。。。
好,到了尾声,来说说为什么朋友问我block会不会引行死循环,我说不会的理由。
见码:
123456789101112- (IBAction)onTest:(id)sender{BlockDemo *demo = [BlockDemo blockdemo];//[[BlockDemo alloc]init];[demo setExecuteFinishedParam:^(BlockDemo * ademo) {if(ademo.resultCode ==200) {NSLog(@"call back ok.");}}];[demo executeTest];}不管是在外面init,还是在里面,且没有加__block 及__weak。为什么,因为我个人常常在使用自己写的block时,如果是回调,比较喜欢把自身当作参数传到block中。这样期实是编译器给我们做了弱引用。因此不会产生循环引用。
由于我一直都这样写block,所以朋友一问起,我就说不会循环引用了,因为压根他碰到的就是前面讲述的哪种访问方式,而我回答的是我的这种使用方式。正因为口头描述,与实际回复真是差之千里。。。哈哈。为了验证我朋友的这个,我特意写了个这篇文章,希望对大家有所帮助。最后,谢谢大家花时间阅读。
写给喜欢用Block的朋友(ios Block)的更多相关文章
- (译)IOS block编程指南 1 介绍
Introduction(介绍) Block objects are a C-level syntactic and runtime feature. They are similar to stan ...
- iOS Block界面反向传值
在上篇博客 <iOS Block简介> 中,侧重解析了 iOS Block的概念等,本文将侧重于它们在开发中的应用. Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C ...
- iOS block的用法
本章学习目标: 1. 了解何谓block. 2. 了解block的使用方法. Block 是iOS在4.0之后新增的程式语法,严格来说block的概念并不算是基础程式设计的范围,对初学者来说也不是很容 ...
- iOS开发:使用Block在两个界面之间传值(Block高级用法:Block传值)
iOS开发:使用Block在两个界面之间传值(Block高级用法:Block传值) 使用Block的地方很多,其中传值只是其中的一小部分,下面介绍Block在两个界面之间的传值: 先说一下思想: ...
- iOS block 机制
本文要将block的以下机制,并配合具体代码详细描述: block 与 外部变量 block 的存储域:栈块.堆块.全局块 定义 块与函数类似,只不过是直接定义在另一个函数里,和定义它的那个函数共享同 ...
- iOS - Block产生Memory Leaks循环引用导致的内存泄漏以及解决方案
在ARC(自动引用技术)前,Objective-c都是手动来分配释放 释放 计数内存,其过程非常复杂. ARC技术推出后,貌似世界和平了很多,但是其实ARC并不等同于Java或者C#中的垃圾回收,AR ...
- iOS - Block的简单使用
Block 的使用有两种: .独立Block .内联Block <一>独立Block 使用方式 一.定义一个Block Object,并调用. 1.定义 // 定义一个Bl ...
- ios Block详细用法
ios Block详细用法 ios4.0系统已开始支持block,在编程过程中,blocks被Obj-C看成是对象,它封装了一段代码,这段代码可以在任何时候执行.Blocks可以作为函数参数或者函数的 ...
- iOS Block的本质(一)
iOS Block的本质(一) 1.对block有一个基本的认识 block本质上也是一个oc对象,他内部也有一个isa指针.block是封装了函数调用以及函数调用环境的OC对象. 2.探寻block ...
随机推荐
- 使用WebService和不使用WebService哪个速度更快哪个更安全
- 在C#中使用官方驱动操作MongoDB
MongoDB的官方驱动下载地址:https://github.com/mongodb/mongo-csharp-driver/releases 目前最新的版本是2.10,支持.NET 4.5以上.由 ...
- mysql修改引擎
1 查看系统支持的存储引擎 show engines; 2 查看表使用的存储引擎 两种方法: a.show table status from db_name where name='table_na ...
- Myeclipse如何设置字体大小
由于Myeclipse一般是英文版的,这就给英语不太好的人带来了一定的麻烦,有时连设置个字体都无法顺利进行!!! 工具/原料 Myeclipse 方法/步骤 双击启动Myeclipse 点击& ...
- 如何在命令行模式下查看Python帮助文档---dir、help、__doc__
如何在命令行模式下查看Python帮助文档---dir.help.__doc__ 1.dir函数式可以查看对象的属性,使用方法很简单,举str类型为例,在Python命令窗口输入 dir(str) 即 ...
- ecshop登录
邮箱登录 a.找到function login(){} ,增加一个邮箱判断is_mail() , b.如果通过,增读取出username , c.再通过默认的login功能 1.需要修改文件incl ...
- Android签名总结
signapk.jar与eclipse export插件默认赋予程序一个DEBUG权限的签名 signapk.jar包含有系统权限(system api, permission),而eclipse e ...
- MySQL 视图的基础操作(五)
1.为什么使用视图: 为了提高复杂SQL语句的复用性和表操作的安全性(例如:工资字段不想展示给所有能查看该查询结果的人),MySQL提供了视图特性.所谓视图,本质上是一种虚拟表,其内容与真实的 ...
- 通过LVS+Keepalived搭建高可用的负载均衡集群系统
1. 安装LVS软件 (1)安装前准备操作系统:统一采用Centos6.5版本,地址规划如下: 服务器名 IP地址 网关 虚拟设备名 虚拟ip Director Server 192.168 ...
- 百度编辑器修改,不让它自动替换html标签
在ueditor.all.js中找到 UE.plugins['defaultfilter'] = function () 注释或删掉这块代码. 注:ueditor.all.min.js 中也一样,格式 ...