iOS Block 中 迴圈引用的解決
前言: 在iOS 中使用block 時 ,如果稍微不注意,則很容易 導致 迴圈引用 導致記憶體洩漏 二者都無法釋放 。出現記憶體洩漏。
#import <Foundation/Foundation.h>
typedefvoid (^EOCNetworkFetcherCompletionHandler)(NSData *data);
@protocolEOCNetworkFetcherDelegate;
@interface EOCNetworkFetcher : NSObject
- (instancetype)initWithURL:(NSURL *)url;
- (void
@property (nonatomic,weak)id<EOCNetworkFetcherDelegate>delegate;
@property (nonatomic,strong,readonly)NSURL *url;
- (void)start;
@end
@protocol EOCNetworkFetcherDelegate <NSObject>
- (void)networkFetcher:(EOCNetworkFetcher
didFinishedWithData:(NSData *)data;
@end
. m
#import "EOCNetworkFetcher.h"
@interfaceEOCNetworkFetcher ()
@property (nonatomic,strong,readwrite)NSURL *url;
@property (nonatomic,copy)EOCNetworkFetcherCompletionHandler completionHandler;
@property (nonatomic,strong)NSData *downloadedData;
@end
@implementation EOCNetworkFetcher
- (instancetype)initWithURL:(NSURL *)url
{
if (self=[superinit]) {
_url=url;
}
returnself;
}
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)handler
{
// 這裡只是設定並沒有調起block模擬假設3s 後返回了資料
self.completionHandler=handler;
[selfperformSelector:@selector(p_requestCompleted) withObject:nilafterDelay:3.0];
}
/**
這段程式碼看上去沒什麼問題實際上在使用的時候會有迴圈引用導致的記憶體洩漏
*/
- (void)p_requestCompleted
{
if (_completionHandler) {
_completionHandler(_downloadedData);
}
}
#pragma mark ---- Block 迴圈引用測試
我們假定 controller 是呼叫者
@interfaceViewController ()<EOCNetworkFetcherDelegate>
@property (nonatomic,strong)EOCNetworkFetcher *fetcher;
@property (nonatomic,strong)NSMutableSet *set;
@end
/**
第一種形式的迴圈引用
*/
- (void)retainCycleBlock
{
//
_fetcher=[[EOCNetworkFetcheralloc]initWithURL:[NSURLURLWithString:@"fooUrlString"]];
//
[_fetcherstartWithCompletionHandler:^(NSData *data) {
// foo handler
_fetchedData=data;
NSLog(@"request completion url is %@",_fetcher.url);
}];
// @property (nonatomic,strong)EOCNetworkFetcher *fetcher;
// handler block保留了 self因為要獲取 _fetchedData
// self 通過 strong 保留了 _fetcher
// 而 _fetcher又保留了 hander 塊那麼三者都無法釋放
}
- (void)solutionOne
{
//
_fetcher=[[EOCNetworkFetcheralloc]initWithURL:[NSURLURLWithString:@"fooUrlString"]];
//
[_fetcherstartWithCompletionHandler:^(NSData *data) {
// foo handler
_fetchedData=data;
NSLog(@"request completion url is %@",_fetcher.url);
/*
能夠打破迴圈引用
但是前提是 completionHandler 執行過後才會解除引用這樣如果沒有執行那麼依然會出現
*/
_fetcher=nil;
}];
}
/**
第二種形式的迴圈引用也就是說呼叫 handler 的物件引用了 handler
這種往往更隱蔽
*/
- (void)retainCycleBlockTwo
{
EOCNetworkFetcher *fethcer=[[EOCNetworkFetcheralloc]initWithURL:[NSURLURLWithString:@"someUrlString"]];
// 為了保持 fethcer 的存活不被回收我們把 fethcer 放進全域性是set 中待網路完成後移除
[self.setaddObject:fethcer];
// 一個要在代理方法中返回一個直接在呼叫的方法中返回程式碼更清晰
[fethcer startWithCompletionHandler:^(NSData *data) {
_fetchedData=data;
NSLog(@"request completion url is %@",fethcer.url);
[_setremoveObject:fethcer];
}];
}
- (NSMutableSet *)set
{
if (!_set) {
_set=[NSMutableSetset];
}
return_set;
}
解決方案是 呼叫 之後 設定 handler 為nil
- (void)p_requestCompletedNoCycle
{
if (_completionHandler) {
_completionHandler(_downloadedData);
}
_completionHandler=nil;
}
很多人說 那我為什麼不 completion handler 作為EOCNetworkFetcher 的公共屬性暴露出來呢?
因為那樣的話 不方便 在執行 請求操作後 將其清理掉了 因為既然 handler 是公共的屬性 ,那麼應該交由呼叫者來做, 而呼叫者不一定會這麼做 他們會吐槽你 封裝的不夠好。