iOS arc下循环引用问题

原文出处:http://blog.cnbang.net/tech/2085/

自己理解

开发时,用老框架ASIHttpRequest请求时(现在使用AFNetWorking),有时候会出现发不出请求的情况,项目开启了ARC,代码如下:

@implement MainController
- (void) fetchUrl{
    ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
    [request setCompletionBlock:^{
        NSLog(@"completed");
    }];
    [request startAsynchronous];
}
@end

排查后发现,request这个对象在执行完setCompletionBlock方法后,被释放掉了,也就不会执行[request startAsynchronous]这里,自然也发不出请求。

解决办法:

由于使用了ARC,也没法手动调用[request remain]让这个变量不被释放,所以只能将这个变量改成实例变量,让Controller存在时一直持有该对象不被释放。

修改成如下:

@interface MainController {
     ASIHTTPRequest *request;
}
@end
 
@implement MainController
- (void) fetchUrl{
    request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
    [request setCompletionBlock:^{
        [self complete];
    }];
    [request setFailedBlock:^{
          NSLog(@"failed");
    }];
    [request startAsynchronous];
}
@end

这样又有新的问题了

XCode编译后提示[self complete]这一行可能会导致循环引用

重点理解“循环引用”处来了

因为MainController实例对象持有属性request,而request持有方法setCompletionBlock,但是setCompletionBlock方法里面又持有MainController实例对象,这样就导致循环引用,MainController实例对象在外面引用计数为0时仍然无法释放,因为request里面持有MainController实例对象的引用,其引用计数永远大于1。

导致循环引用的原因在于setCompletionBlock里面调用的self是一个strong类的引用,会使self引用计数+1。解决方法就是声明一个__weak变量指向self,这样block使用这个变量时就不会导致self引用计数+1,不会导致循环引用。

@implement MainController
- (void) fetchUrl{
     request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
     __weak id this = self;
 
    [request setCompletionBlock:^{
        [this complete];
    }];
    [request startAsynchronous];
}
@end

新的问题,在block中如果只是调用MainController的方法,上面就能完美解决问题,但是现在需要在block中调用很多实例变量,包括赋值等,如下

@interface MainController {
     ASIHTTPRequest *request;
     BOOL isLoading;
     UIView *loadingView;
}
@end
 
@implement MainController
- (void) fetchUrl{
    request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
 
    [request setCompletionBlock:^{
        isLoading = NO;
        loadingView.hidden = NO;
    }];
    [request startAsynchronous];
}
@end

xcode提示说isLoading = NO和loadingView.hidden = NO两行可能循环引用,解决方法如下:

实例变量全部加上get set方法,通过弱引用对象访问即可,缺点是破坏了封装性,把原本私有的实例变量变成公有的。

@interface MainController {
     ASIHTTPRequest *request;
}
@property (nonatomic, strong) UIView *loadingView;
@property (nonatomic, assign) BOOL isLoading;
@end
 
@implement MainController
@synthesize loadingView, isLoading;
 
- (void) fetchUrl{
     request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];
     __weak id this = self;
 
    [request setCompletionBlock:^{
        this.isLoading = NO;
        this.loadingView.hidden = NO;
    }];
    [request startAsynchronous];
}
@end


新框架(AFNetworking)如下方法解决上述问题

__weak __typeof(&*self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(&*weakSelf)strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};




你可能感兴趣的:(iOS arc下循环引用问题)