iOS arc下循環引用問題

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

本身理解框架

開發時,用老框架ASIHttpRequest請求時(如今使用AFNetWorking),有時候會出現發不出請求的狀況,項目開啓了ARC,代碼以下:fetch

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

排查後發現,request這個對象在執行完setCompletionBlock方法後,被釋放掉了,也就不會執行[request startAsynchronous]這裏,天然也發不出請求。
this

解決辦法:atom

因爲使用了ARC,也無法手動調用[request remain]讓這個變量不被釋放,因此只能將這個變量改爲實例變量,讓Controller存在時一直持有該對象不被釋放。spa

修改爲以下:.net

@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

這樣又有新的問題了code

XCode編譯後提示[self complete]這一行可能會致使循環引用對象

重點理解「循環引用」處來了
blog

由於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);
}
};
相關文章
相關標籤/搜索