ARC模式下的循环引用引起内存泄漏
自从苹果手机操作系统iOS 5那个时期自动引用计数也就是Automatic Reference Counting的技术被发布出来,Cocoa工程师们才抛开了内存管理方面的负担,从这时候起在Objective-C修炼道路上的一座大山就被铲除了。可是,即便自动引用计数也就是ARC十分强大,我们在日常从事的工作当中同样是存在内存泄漏风险的,今天我就跟大家说一说这些你或许还没有留意到的隐患末端的这个实心点。
测试原理
我们清楚,在ARC模式情形下,NSObject的MRC相关方法均不可再使用了,然而,倘若dealloc方法被实现了,同样依旧会被调用的,只是在dealloc方法当中不被允许进行调用 。
super dealloc
,因此我们于dealloc方法里面添加log信息,如此便能够追踪到我们的实例是不是被释放了 。
容易忽视的引用循环
我们清楚引用计数内存管理的设计观念,此观念是依据实例的计数值,以此来判定是否去解除对实例内存空间的占用。
比如说,我们的ViewController里,有一个属于block类型的property
有一个属性,它的类型是无参数无返回值的 void 类型的代码块,这个属性是非原子的,并且是强引用类型的,它叫做 testBlock;
我们在viewDidLoad中加入如下代码
[self setTestBlock:^{
self.title = @"测试";
}];
这段代码,从表面进行观察,不存在任何问题,然而编译器,却将会给出警示
经翻译后其意思为,当于block里对self指针加以使用时,极有可能会引发一个引用循环现象出现,致使self不能够被释放掉 。
什么是引用循环(retain cycle)
假定咱存在两个实例A以及B,B是A的属于strong类型的一个property,那么B的引用计数为1,在A需要被释放之际,A就会去调用
B release
来释放B,B的引用计数则减为0,释放。
可要是在这个时候,把B的一个属于strong类型的property指向A,那么A和B就会彼此之间出现强引用情况,紧接着问题就产生了。由于B对A进行强引用,所以A的引用计数永远都没办法减少到0,在A原本的强引用对象被释放之后,A和B就变成了一个相互引用的孤立区域,永远都不会被释放掉,这样就会引发内存泄漏 。
在上面所举为例之中,呈现存在一种极为普遍的引用循环状况,将像上述代码那样的VC在 Dismiss 或者 Pop 这下子后,并不产生什么情况是会去执行 dealloc 方法的,这也就证实了内存泄漏这一情况已经就此发生了。而致使出现泄漏这种情况的缘由就在于在身为作为自我的 property 的 block 之中, utilize self 指针从而使得 self 被 block 强力引用,进而形成了引用循环 。
如何解决引用循环问题
在编译器给出上面所述的warning之时,绝对不可以予以忽视,正确的解决办法是像下面这样子:
让一个名为weakSelf的,类型是既不保证安全又非强引用的持有Demo1ViewController的指针,指向当前自对象 。
[self setTestBlock:^{
weakSelf.title = @"测试";
}];
又亦或运用__weak同样可行,其原理着实相当简易,亦即是对一个弱引用对象予以声明,于block里用以替换self,如此这般处于咱们的测试期间,下述代码便可成功实现输出log,此为证明VC被正确予以释出之标志 。
- (void)dealloc
{
NSLog(@"%s",__func__);
}

二零一六,零九,零七,十三,十七,三十八,八七九,反应式可可演示
7473:3432323
Demo1ViewController dealloc
其他会引起引用循环的状况NSTimer
NSTimer在VC释放前,一定要调用
timer invalidate
,不进行调用所产生的后果便是NSTimer没办法释放它的target,要是target恰好就是self(也就是VC本身),那么就会出现引用循环。
在此处需补充一点,引用循环并非仅能有两个对象,三个、四个乃至更多个对象都是可行的,甚至环数也不见得只有一个,所以要养成良好的代码习惯,在NSTimer停用之前调用invalidate方法。
WKUserContentController
使用WKWebView时,这个类通常会配套使用,要是你发觉项目里调用了addScriptMessageHandler方法,那就得留意了,查看在VC释放前有无对称调用removeScriptMessageHandlerForName方法,若没有就会引发引用循环。
调用方法如下:
从[self.wkWebView.configuration.userContentController]里,移除针对名为:@"qdpay"的脚本消息处理程序;
留意一下,存在一个WKWebViewConfiguration,它包含在WKUserContentController以及WKWebView当中。
引用大循环
就如同先前讲的那样,引用循环没准是个大循环 ,我碰到过一种情形 ,那就是给UITableViewCell设置block属性去响应事件 ,在block里强引用了self ,致使self到tableView再到cell最后又回到self构成了循环 。
改善block写法避免强引用self
要是想从根源上扭转这种容易出现的错误,那就得从书写方式着手改变,着手去避免。把上面的代码按照如下方式改写:
把[ self setTestBlock:^(__kindof UIViewController * vc) ],拆分成:[ self setTestBlock:^. ],[ __kindof UIViewController * vc ],。 (注:按要求改写后句子逻辑较混乱,原内容基于特定编程语言语法结构,这样改写失去了原本语义,仅为满足形式要求)
vc.title = @"123";
}];
self.testBlock(self);
把self当作参数传入block即可防止强引用,就逻辑方面而言,代码更为健壮。
结束
把上面所列举的,由几个引用循环引得的内存泄漏,编译器未给出任何提示,且不影响App的运行,不会出现crash,然而身为严谨的程序猿,我等无法对这类小泄漏予以容忍,虽说其并不会左右大局,可聚沙成塔终究会对系统的运行速度造成影响。