@class MyObjectB; @interface MyObjectA : NSObject @property (nonatomic, strong) MyObjectB *objectB; @end @implementation MyObjectA - (void)dealloc { NSLog(@"%s",__func__); } @end @class MyObjectA; @interface MyObjectB : NSObject @property (nonatomic, strong) MyObjectA *objectA; @end @implementation MyObjectB - (void)dealloc { NSLog(@"%s",__func__); } @end
运行以下程序:
MyObjectA *objectA = [[MyObjectA alloc]init]; MyObjectB *objectB = [[MyObjectB alloc]init]; objectA.objectB = objectB; objectB.objectA = objectA; objectB = nil; objectA = nil;
并无打印信息,没有调用dealloc方法,因为形成了强引用环,如下:
这里objectA和objectB形成了环(3和4)
objectA = nil; objectB = nil;
调用以上代码以后,如下:
objectA = nil;执行的时候,objectA的引用计数实际上是从2变成了1,因为此时还有objectB强引用它;
objectB = nil;执行的时候,objectB的引用计数实际上也是从2变成了1,因为此时还有objectA强引用它,虽然之前objectA指针无效了,但是objectA这块内存还没有被销毁;
这两行代码都没有打破环;
而此时没有任何指针指向这两块内存了,于是发生内存泄漏。
要解除循环引用,就必须打破环(3和4),有两种方式可以打破环:
方法一:
解除修改属性的引用方式如下:
@property (nonatomic, weak) MyObjectB *objectB;
方法二:
直接解除3或者4,如下:
{ MyObjectA *objectA = [[MyObjectA alloc]init]; MyObjectB *objectB = [[MyObjectB alloc]init]; objectA.objectB = objectB; objectB.objectA = objectA; objectB.objectA = nil; //断点一 } //断点二
在断点二处有打印信息:
-[MyObjectA dealloc]
-[MyObjectB dealloc]
在断点一出解除了引用环,在断点二处,作用域结束,objectA和objectB都释放。
再来看一个循环引用的实例:
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"]; _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url]; [_networkFetcher startWithCompletionHandler:^(NSData *data){ _fetchData = data; }];
打破环方式:
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"]; _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url]; [_networkFetcher startWithCompletionHandler:^(NSData *data){ _fetchData = data; _networkFetcher = nil; }];
并保证CompletionHandler会调用。
这里把_networkFetcher = nil;提到block外面来也能打破环。
还有种方式就是不要将_networkFetcher作为controller的实例变量,而是局部变量来处理,也可以防止引用环。下面会讲到。
原因如下:
这里4,5,6形成了环,只要任意解除其中一个就行,将_networkFetcher设置为nil,实际上就是解除了4,因为这里的_networkFetcher就是controller的实例,即2就是4。
但是你不要指望在controller的dealloc方法中去将_networkFetcher置为nil,因为你只是解除了1,所以dealloc根本不会被调用。
继续看下面的例子:
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"]; EOCNetworkFetcher *_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url]; [_networkFetcher startWithCompletionHandler:^(NSData *data){ _fetchData = data; }];
有人通过这种方式来避免循环引用,但是很可能造成另外的环,如下:
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"]; EOCNetworkFetcher *_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url]; [_networkFetcher startWithCompletionHandler:^(NSData *data){ NSLog(@"Request URL %@ finished", _networkFetcher.url); _fetchData = data; }];
_networkFetcher和completionHandler之间形成了环,这个解决方法就是,最好是在block中将_networkFetcher作为参数传进来使用,而不是直接使用_networkFetcher。
最后我们再来看一个复杂一点的:
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"]; _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url]; [_networkFetcher startWithCompletionHandler:^(NSData *data){ NSLog(@"Request URL %@ finished", _networkFetcher.url); _fetchData = data; }];
这里看似有两个环,我们要怎么打破环呢?
打破环方式:
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"]; _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url]; [_networkFetcher startWithCompletionHandler:^(NSData *data){ NSLog(@"Request URL %@ finished", _networkFetcher.url); _fetchData = data; _networkFetcher = nil; }];
并保证CompletionHandler会调用。
这里把_networkFetcher = nil;提到block外面来也能打破环。
原因如下:
这里4,5,6形成了环,5和7也形成了环,将_networkFetcher设置为nil,实际上就是解除了2,4和7,2不用解释,说解除4是因为这里的_networkFetcher就是controller的实例,是同一个指针_networkFetcher,说解除7是因为completionHandler捕获的也是_networkFetcher指针所指的对象。