原文出处: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); } };