C语言有关Block的运用和缓解Cycle Retain问题(ARC)

   
 本文只介绍了ARC时的图景,有些细节不适用于MRC。比如MRC下__block不会增多引用计数,但ARC会,ARC下必须用__weak指明不增添引用计数;ARC下block内存分配机制也与MRC不一样(ARC下会将栈区的Block在赋值的时候copy到堆区,从而造成截取的堆区变量引用计数扩张),所以文中的一部分例子在MRC下测试结果可能与文中描述的不均等

简介:这是一篇讲解咋样利用Block,以及在运用过程中什么避免Cycle
Retain的稿子。如若想要知道Block的深层次的落实,能够去看<Objective-C
高级编程 iOS与OS
X多线程和内存管理>的Block篇,书中详解了Block的底层实现。

一、Blcok的�优点和花色

 1、Block的优点

      Block虽然会出于使用不当,而招致Cycle
Retain,但要么有过多优点的。语法简洁,回调方便,思路清楚,还有就是Block作为C语言的恢宏执行效能较高。这样用文字表达可能不�直观,直接上代码做比较。布告的设计格局是开发进程中常用的,以使用Block回调和不使用Block的点子来作相比较。

图一:对比

   
通过对照,使用Block的吸纳公告处理和通告接收的不二法门紧密的黏在一起,直观明了,不过这里有个大坑,待会会波及。是否感受到Block的便宜了吧,假假诺,那么之后就多用吧,它会让您的代码思路更连贯!

2、Block的种类

   
Block不就是匿名函数么,还有项目?这些类型不是说形式上的档次,而是基于Block在内存中存储区域的例外而分的系列,有二种:Stack(栈区),Malloc(堆区),Global(全局)。之所以要在这边提到这两种Block,是因为前边的Cycle
Retain就是由于Malloc(堆区)的Block导致的。在OC中堆区的内存管理都是用引用计数来治本的,而Stack和Global都是尚未引用计数的,当它们超出功效域后,就会失掉功效。那么Stack(栈区),Malloc(堆区),Global(全局)的block怎么判断,它们各自有什么样呢。

(1)判断方法

图二:判断Block的内存区域

   
在代码中,咱们定义了一个大局静态区的变量,通过它和block地址的自查自纠,可以发现它们大多,也就是说这些Block是Global(全局)的。同样的章程,Stack(栈区),Malloc(堆区),都得以判断出来。倘若你觉得这种判断方法太low的话,Clang可以查看中间代码(C++),打开终端用Clang
-rewrite-objc
编译你的文书,就足以观望中间代码了。说了不说原理的,不然太长了。假设想用这种办法判断的话,可以去看望这篇博客:iOS中block贯彻的探赜索隐

(2)Stack(栈区),Malloc(堆区),Global(全局)的Block有哪些

   以下所说的都是在ARC格局下

图三:各类品种的Block

二、Block的使用

   
之所以写这一有的,是因为有的初学者,连基本的Block都不会采用,也不精晓用在怎么动静下,下边就是说Block用在如何情形下,又怎么用,假若你早就会用了,可以跳过这一局部。

1、用于多少个类之间的通信

 
 这是付出中最常用的,也就是ViewController和View,ViewController和ViewController之间的通信,这多少个通信就包括传值或者让另一个对象举行一些处理。这几个思路和delegate(代理)很像,可是Block更简洁。这里就不上代码了,因为代码实在是欠好上啊!要是真的需要的可以私聊我。

2、用于�方法的回调

   
这种利用状态,也是常用的,系统和成千上万第三方都用了如此的艺术。仍旧在此之后边接收通知的Block为例子

图四:通知焦点用的Block

 
 我们来分析一下以此情势的末段一个参数usingBlock,跟后面一样,在:后边都是跟的参数类型,那么usingBlock后边也是跟的参数类型,那么那一个参数类型就是从未再次回到值、参数为note(NSNotification类的对象)的Block类型(前面的block为参数名)。那么接下去,我们就和好定义一个近乎的办法,让它有回调Block

图五:回调Block

 
 这样,我们就定义了一个平素不再次来到值,没有参数的Block类型,这一个类其它变量为block,并且在函数内部贯彻回调,这样,大家就落实了和眼前系统通报所写的相同的Block回调。当然在写Block类型的时候,是不会如此写的,而是用typedef。

那就是Block的两种常用用法,当然这是最要旨的。下边就进来本文的紧要,怎么样制止在使用Block的过程中造成的Cycle
Retain。

三、避免Cycle Retain

1、Cycle Retain

      retain
cycle问题的根源在于Block和obj可能会互相强引用,Malloc(堆区)Block的内存管理措施也是援引计数,它的其中贯彻和类一样,都是经过isa指针指向堆区的该品种对象,可以说Malloc(堆区)Block就是一个类的目标,而被block截取的变量,就视作它的”属性”,会被retain五遍依然copy到堆区(假使它是在栈区的话)),相互retain对方。比如A和B五个对象,A持有B,B同时也持有A,遵照地方的条条框框,A只有B释放之后才有可能释放,同样B唯有A释放后才可能释放,当二者都在守候对方释放的时候,
retain cycle就形成了,结果是,七个目的都永远不会被保释,最后内存泄露。

图六:互相持有(Cycle Retain)

基于那些原理,那么会造成Cycle Retain的状态就只有两种。

一种是:block作为某个类的性能,但是它又截取了这些类的目的,从而致使Block
retain了四次这多少个目的,这些目标又retain了五回这个Block(作为性能的时候会用copy,引用计数加一)。以ViewController这些类为例

图七:block作为性能

我们发现这种意况,xcode会给大家警示,所以这种状态是很容易发现并解决的,用__weak
typeof(self) weakself = self;来顶替block里面的self,就足以了。

第二种:这种处境很难发现,但是很好解决(解决方法同样)。这是咋样呢,其实本质仍然如出一辙,就是一个类的目的retain或者copy了这一个Block,而以此Block又同时兼有了这一个类的目的,导致相互不可能假释,因为block无法自由,导致其他被这一个block截取的靶子也无从自由。要么以公告为例(请见谅自己,我确实顶级喜欢用文告~)

图七:对象被没有自由的block持有

   
这段代码的思绪是,当自家接受到通报的时候,我就改变ViewController的颜料,然后在当ViewController释放的时候移除通告。不过这会导致Cycle
Retain,导致ViewController不能够假释。解决办法你或许也领略,跟上边一样,block里面放weakself。不过怎么吧?这个Block我们从未当做性能,ViewController并没有retain它,只是Block
retain了ViewController而已,没有导致Cycle Retain。我们先看一段官方文档:

图八:通告参数block的官方表达

翻译一下:这一个block会再接收到通告的时候实施,以此block被通报中心copy并且直到观看者被移除的时候才会移除。也就是说这些block会一贯被通报中央具有,直到观看者被移除,它才会被放飞。很好,问题解决了。block平素被通报中央具有,而block又retain了一遍ViewController,导致ViewController不可能假释(引用计数无法为0),这样ViewController就不会走dealloc那一个艺术。解决办法也是均等:

图八:解决办法

第三种:这种情景和第两种情状原理一样,可是是最常遭逢的,所以单独拿出来讲。这种景观是在档次中,用MJRefresh这多少个第三方的时候发现的。其实,只要懂了Cycle
Retain的题材根源,这种境况也是很好精通的。

tableView.mj_footer = [MJRefreshFooter
footerWithRefreshingBlock:^(void)refreshingBlock]

当tableView举行上拉加载的时候,会接触那一个这么些回调refreshingBlock,执行相应的加载操作(跟新数据),倘使在refreshingBlock里面用了self,也会导致Cycle
Retain,这那又是干什么吧。把那多少个方法点进去之后可以见见它的贯彻:

图九:方法的其中贯彻

可以看到,方法的贯彻中,把block作为属性�赋值给MJRefreshFooter对象还要重回作为tableView的特性。我们精通所有的View都被ViewController
retain了一回(view的生活周期),假诺block作为view的性质,这就相当于self.view.tableView.mj_footer.refreshingBlock;所以refreshingBlock前边所有的目标:self、tableView、mj_footer都不可以被refreshingBlock
retain,假诺有一个被retain了,这就是Cycle
Retain!�这里我们依然用__weak指针打破Cycle
Retain。解决方法一致,这里就不详解了。

2、��不可以滥用__weak指针

    __weak指针可以缓解Cycle
Retain问题,不过无法乱用比如gcd和UIView的Animation等等,因为Block没有retain那多少个目标,即使不会像MRC下这样造成Crash,不过依然可能会促成没法实现您要的机能。例子如下:

图十:乱用__weak指针

 
 这里我们让dispatch_async中的队列延迟5秒执行,�在推行队列前按下button,让self释放掉(dissmiss),这样self会为nil,可是我想要在5秒后让它输出”test”,由于self已经被放飞变为nil,即使不会crash或者内存泄露,不过自己想要实现的效能却不可能促成了。

     
将Block作为参数传给dispatch_async时,系统会将Block拷贝到堆上,尽管Block中使用了实例变量,还将retain
self,因为dispatch_async并不知道self会在啥时候被放出,为了保证系统调度执行Block中的任务时self没有被意外释放掉,dispatch_async必须协调retain两次self,任务成功后再release
self。但那里运用__weak,使dispatch_async没有扩充self的引用计数,那使得在系统在调度执行Block在此之前,self可能已被销毁,但系统并不知道这多少个意况,可能造成有些效果无法促成。

   
总计:要想用好Block就得多写、多用,当Block作为性能的时候,就值得您去关爱Retain
Cycel的问题了。

   
 最终也是最着重的,如若有用到Block,�尽量在分外类里写下-(void)dealloc那些法子,看看那么些类本该释放�是否没有自由,�假若没有自由,再去商量并缓解!这样积累的经历越多,相信看理论知识也能看得更深。