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

自从iOS 5时代自动引用计数(Automatic Reference Counting)技术发布,Cocoa工程师们才扔下了内存管理的包袱,从此在Objective-C修行道路上的一座大山被削平。然而,即使ARC很强大,我们日常搬砖时同样是有内存泄漏风险的,今天我就跟大家聊聊这些你可能还没有注意到的坑。

测试原理

我们知道ARC模式下,NSObject的MRC相关方法都不可以使用了,但dealloc方法如果实现了,同样还是会调用的,只是不允许在dealloc方法中调用[super dealloc],所以我们在dealloc方法中加入log信息就可以跟踪到我们的实例是否释放。

容易忽视的引用循环

我们知道引用计数内存管理的设计理念,就是根据实例的计数值来决定是否释放实例内存空间。

例如我们的ViewController拥有一个block类型的property


  1. @property (nonatomic, strong) void (^ testBlock)(void); 

我们在viewDidLoad中加入如下代码


  1. [self setTestBlock:^{ 
  2.     self.title = @"测试"; 
  3. }];  

这个代码从表面上看没有什么问题,但编译器会给出warning,

Catering 'self' strongly in this block is likely to lead a retain cycle

翻译过来意思是在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方法,证明内存泄漏了。而引起泄漏的原因就是在作为self的property的block中,使用self指针导致self被block强引用,形成引用循环。

如何解决引用循环问题

在编译器提示上面的warning的时候一定不要忽视,正确的解决办法如下:


  1. __unsafe_unretained Demo1ViewController * weakSelf = self; 
  2. [self setTestBlock:^{ 
  3.        weakSelf.title = @"测试"; 
  4. }];  

或者使用__weak也可以,原理也很简单,就是声明一个弱引用对象在block中替代self,这样在我们测试中,下面代码就能正常输出log,标志着VC被正确释放。


  1. - (void)dealloc 
  2.     NSLog(@"%s",__func__); 
  3. }  

2016-09-07 13:17:38.879 ReactiveCocoaDemo[7473:3432323] -[Demo1ViewController dealloc]

其他会引起引用循环的状况

NSTimer

NSTimer在VC释放前,一定要调用[timer invalidate],不调用的后果就是NSTimer无法释放其target,如果target正好是self(VC本身),则引用循环。

这里要补充一点,引用循环不是只能有两个对象,三个四个更多都是可以的,甚至环数也不一定只有一个,所以要养成良好的代码习惯,在NSTimer停用前调用invalidate方法。

WKUserContentController

这个类一般会在使用WKWebView的时候配套使用,如果你发现项目中调用了addScriptMessageHandler方法,就要注意了,检查有没有在VC释放前对称调用removeScriptMessageHandlerForName方法,如果没有则引起引用循环。

调用方法如下:


  1. [self.wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"qdpay"]; 

注意WKUserContentController和WKWebView中还有一个WKWebViewConfiguration。

引用大循环

就像前面说的,引用循环可能是一个大循环。我遇到过一种情况,就是给UITableViewCell设置block属性响应事件,在block中强引用了self,导致self->tableView->cell->self形成循环。

改善block写法避免强引用self

如果要从根本改变这种易发的错误,要从写法开始改变,开始避免。将上面的代码改写如下:


  1. @property (nonatomic, strong) void (^ testBlock)(__kindof UIViewController* sender);  
  2.  
  3. [self setTestBlock:^(__kindof UIViewController * vc) { 
  4.         vc.title = @"123"; 
  5.  }]; 
  6.      
  7.  self.testBlock(self);  

将self作为参数传入block即可避免强引用,从逻辑角度来看,代码更健壮。

结束

上面列举的几个引用循环引起的内存泄漏,编译器是没有任何提示的,并且也不影响App运行,不会crash,但作为严谨的程序猿,我们不能容忍这种的小泄漏,虽然不影响大局,但积少成多终将影响系统的运行速度。

作者:秋刀生鱼片

来源:51CTO

时间: 2024-11-05 12:26:41

ARC模式下的循环引用引起内存泄漏的相关文章

ios-IOS获取系统相册图片名称在非ARC模式下报错

问题描述 IOS获取系统相册图片名称在非ARC模式下报错 dispatch_async(dispatch_get_main_queue(), ^{ ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset){ ALAssetRepresentation *representation = [myasset defaultRepresentation]; NSString *fileName = [represen

怎么用弱引用实现内存泄漏检测

在Java中,引用分为强引用.软引用.弱引用和虚引用四种. 强引用,代码中普遍存在的形式,例如常见的普通类new出对象后的引用.GC不会回收强引用的对象. 软引用,软引用对象会在内存溢出异常之前进行回收,也就是说在内存富裕的情况下GC不回收软引用.它可通过SoftReference类实现. 弱引用,弱引用对象会在下一次GC时被回收,也就是说不管内存富不富裕,当GC时都会回收弱引用.它可通过WeakReference类实现. 虚引用,虚引用不会改变对象的生存时间,它只是让对象在被GC时能收到一个系

基于若引用的内存泄漏检测

在Java中,引用分为强引用.软引用.弱引用和虚引用四种. 强引用,代码中普遍存在的形式,例如常见的普通类new出对象后的引用.GC不会回收强引用的对象. 软引用,软引用对象会在内存溢出异常之前进行回收,也就是说在内存富裕的情况下GC不回收软引用.它可通过SoftReference类实现. 弱引用,弱引用对象会在下一次GC时被回收,也就是说不管内存富不富裕,当GC时都会回收弱引用.它可通过WeakReference类实现. 虚引用,虚引用不会改变对象的生存时间,它只是让对象再被GC时能收到一个系

Java中用软引用阻止内存泄漏

在本文中,他将解释 Reference 对象的另外一种形式,即软引用(soft references),用于帮助垃圾收集器管理内存使用和消除潜在的内存泄漏. 垃圾收集可以使 Java 程序不会出现内存泄漏,至少对于比较狭窄的 "内存泄漏" 定义来说如此,但是这并不意味着我们可以完全忽略 Java 程序中的对象生存期(lifetime)问题.当我们没有对对象生命周期(lifecycle)引起足够的重视或者破坏了管理对象生命周期的标准机制时,Java 程序中通常就会出现内存泄漏.例如,上一

PHP对象递归引用造成内存泄漏分析_php技巧

通常来说,如果PHP对象存在递归引用,就会出现内存泄漏.这个Bug在PHP里已经存在很久很久了,先让我们来重现这个Bug,示例代码如下: <?php class Foo { function __construct() { $this->bar = new Bar($this); } } class Bar { function __construct($foo) { $this->foo = $foo; } } for ($i = 0; $i < 100; $i++) { $ob

block循环引用解决

block循环引用解决 实验代码 @interface ViewController ()@property (nonatomic, strong) TestNetworkBlock *testNetwork;@end @implementation ViewController (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a

如何在linux下检测内存泄漏(转)

  本文转自:http://www.ibm.com/developerworks/cn/linux/l-mleak/ 本文针对 linux 下的 C++ 程序的内存泄漏的检测方法及其实现进行探讨.其中包括 C++ 中的 new 和 delete 的基本原理,内存检测子系统的实现原理和具体方法,以及内存泄漏检测的高级话题.作为内存检测子系统实现的一部分,提供了一个具有更好的使用特性的互斥体(Mutex)类.   1.开发背景 在 windows 下使用 VC 编程时,我们通常需要 DEBUG 模式

深入理解JavaScript程序中内存泄漏_javascript技巧

垃圾回收解放了我们,它让我们可将精力集中在应用程序逻辑(而不是内存管理)上.但是,垃圾收集并不神奇.了解它的工作原理,以及如何使它保留本应在很久以前释放的内存,就可以实现更快更可靠的应用程序.在本文中,学习一种定位 JavaScript 应用程序中内存泄漏的系统方法.几种常见的泄漏模式,以及解决这些泄漏的适当方法. 一.简介 当处理 JavaScript 这样的脚本语言时,很容易忘记每个对象.类.字符串.数字和方法都需要分配和保留内存.语言和运行时的垃圾回收器隐藏了内存分配和释放的具体细节. 许

【转贴】了解 JavaScript 应用程序中的内存泄漏

转贴:http://www.ibm.com/developerworks/cn/web/wa-jsmemory/ 检测和解决内存问题 Ben Dolmar, 软件开发人员, The Nerdery   简介: 垃圾回收解放了我们,它让我们可将精力集中在应用程序逻辑(而不是内存管理)上.但是,垃圾收集并不神奇.了解它的工作原理,以及如何使它保留本应在很久以前释放的内存,就可以实现更快更可靠的应用程序.在本文中,学习一种定位 JavaScript 应用程序中内存泄漏的系统方法.几种常见的泄漏模式,以