当前位置: 首页 > 科技观察

ARC模式下的循环引用导致内存泄漏

时间:2023-03-14 09:59:45 科技观察

自从iOS5时代发布了自动引用计数(AutomaticReferenceCounting)技术后,Cocoa工程师卸下了内存管理的重担,从此成为了Objective-C实践之路被抹平。但是,即使ARC很强大,我们每天搬砖的时候还是有内存泄漏的风险。今天就和大家聊一聊这些你可能没有注意到的陷阱。测试原理我们知道,在ARC模式下,NSObject的MRC相关方法是不能使用的,但是如果实现了dealloc方法,还是会调用,但是在dealloc方法中不允许调用[superdealloc],所以我们在dealloc方法中添加日志信息来跟踪我们的实例是否被释放。容易被忽略的引用循环我们知道引用计数内存管理的设计理念是根据实例的计数值来决定是否释放实例内存空间。例如,我们的ViewController有一个块类型property@property(nonatomic,strong)void(^testBlock)(void);我们在viewDidLoad中添加如下代码[selfsetTestBlock:^{self.title=@"Test";}];这段代码表面上没有问题,但是编译器会给出一个警告,Catering'selfstronglyinthisblockislikelytoleadaretaincycle翻译的意思是在块中使用self指针可能会导致引用循环,导致self没有被释放。什么是引用循环(retaincycle)假设我们有两个实例A和B,B是A的强属性,那么B的引用计数为1,当A需要释放时,A会调用[Brelease]来释放B,B的引用计数减为0,释放。但是如果此时B的一个强属性指向了A,那么A和B就是彼此的强引用,问题就来了。因为B强引用A,所以A的引用计数永远不会减为0。当A原来的强引用对象被释放时,A和B成为互引用的孤岛,永远不会被释放,会造成内存泄漏.在上面的例子中,是一种很常见的引用循环情况。上面代码的VC在dismiss或者pop之后并不会执行dealloc方法,证明是内存泄漏。泄漏的原因是在self属性的block中,使用self指针导致self被block强引用,形成引用循环。编译器提示上述警告时,一定不能忽略如何解决引用循环问题。正确的解法如下:__unsafe_unretainedDemo1ViewController*weakSelf=self;[selfsetTestBlock:^{weakSelf.title=@"Test";}];或者用_weak也可以,原理很简单。就是声明一个弱引用对象来替换block中的self,这样在我们的测试中,下面的代码可以正常输出log,说明VC正确释放了。-(void)dealloc{NSLog(@"%s",__func__);}2016-09-0713:17:38.879ReactiveCocoaDemo[7473:3432323]-[Demo1ViewControllerdealloc]其他导致引用循环的情况NSTimerNSTimer在VC中发布在调用[timerinvalidate]之前,一定要先调用[timerinvalidate]。不调用的后果是NSTimer不能释放它的目标。如果目标恰好是self(VC本身),就会发生引用循环。这里要补充一点,引用循环并不只有两个对象,三四个也是有可能的,甚至循环数也不一定只有一个,所以还是要养成良好的代码习惯,在NSTimer之前调用被禁用。使方法无效。WKUserContentController类一般在使用WKWebView的时候使用。如果发现项目中调用了addScriptMessageHandler方法,就要注意了。检查VC发布前是否对称调用了removeScriptMessageHandlerForName方法。否则会造成引用循环。调用方法如下:[self.wkWebView.configuration.userContentControllerremoveScriptMessageHandlerForName:@"qdpay"];注意WKUserContentController和WKWebView中也有一个WKWebViewConfiguration。引用的大循环正如前面提到的,引用的循环可以是一个大循环。我曾经遇到过这样一种情况,UITableViewCell响应事件设置了block属性,block中强引用了self,导致self->tableView->cell->self的循环。改进块编写以避免对self的强引用。要想从根本上改变这种容易出错的现象,就必须从改写开始,避免这种写法。将上面的代码重写如下:@property(nonatomic,strong)void(^testBlock)(__kindofUIViewController*sender);[selfsetTestBlock:^(__kindofUIViewController*vc){vc.title=@"123";}];self.testBlock(自己);将self作为参数传递到块中可以避免强引用。从逻辑的角度来看,代码更健壮。结束上面列举的几种引用循环造成的内存泄漏,编译器没有给出任何提示,不影响App的运行,不会崩溃,但是作为严谨的程序员,我们不能容忍这么小的泄漏。影响全局,但累积效应最终会影响系统的运行速度。