在iOS开发中,对象之间形成引用循环是一个很大的问题,它会让内存无故被占,甚至还有可能影响通知的接收等。我们在写代码的时候各种小心,但有时候还是避免不了掉入陷阱。下面是几种常见的循环引用示例:
1 . 两个对象通过强引用类型的属性相互持有。
ClassA *aObj = [ClassA new];
ClassB *bObj = [ClassB new];
aObj.b = bObj;
bObj.a = aObj;
2 . 对象通过拥有的Block属性引用自己。
@interface DetailViewController ()
{
void (^_handlerBlock)();
}
@end
@implementation DetailViewController
- (void)viewDidLoad {
[super viewDidLoad];
_handleBlock = ^{
NSLog(@"%@", self);
};
}
@end
这一种情况可以称得上是最隐蔽的了。因为self
不一定需要明写,而是通过实例变量隐含地引入。
3 . 通过成为正在运行的定时器的Target而被持有。这种情况可能许多初学者都不太会注意到,比如在一个页面启动定时器后,喜欢在dealloc
中写上定时器的invalidate
方法。其实只要定时器不停止,该页面就不会销毁,更不会执行dealloc
方法。
诸如此类的引用循环还有许多,一不小心就会掉入坑中。在许多时候,我们都可以使用Xcode提供的Instruments检测内存泄漏(Leaks),但这些引用循环导致的内存泄漏有时却被忽略了。为了解决这些问题,Facebook发布了一个叫FBRetainCycleDetector
的工具,专门用于检测对象是否存在引用循环。它的使用非常简单,我们可以通过CocoaPods或者直接从GitHub下载源码。
pod 'FBRetainCycleDetector'
这个工具能够检测指定对象的引用情况,并把所存在的引用循环中各对象和引用在终端进行打印。
#import <FBRetainCycleDetector/FBRetainCycleDetector.h>
_handlerBlock = ^{
NSLog(@"%@", self);
};
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"%@", retainCycles);
打印结果类似于:
{(
(
"-> DetailViewController ",
"-> _handlerBlock -> __NSMallocBlock__ "
)
)}
很明显,DetailViewController
通过_handlerBlock
实例变量引用一个Block对象,而该Block又引用了DetailViewController
对象。如果不存在引用循环的话,打印的结果将是空的。