@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指针所指的对象。