使用块时,若不仔细思量,很容易导致"保留环"。
// 网络数据获取器,提供了一套从URL中下载数据的接口:
// EOCNetworkFetcher.h
#import <Foundation/Foundation.h>
typedef void(^EOCNetworkFetcherCompletionHandler)(NSData *data);
@interface EOCNetworkFetcher : NSObject
@property(nonatomic, strong, readonly) NSURL *url;
- (id)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;
@end
// EOCNetworkFetcher.m
#import "EOCNetworkFetcher.h"
@interface EOCNetworkFetcher ()
@property(nonatomic, strong, readwrite) NSURL *url;
@property(nonatomic, copy) EOCNetworkFetcherCompletionHandler completionHandler;
@property(nonatomic, strong) NSData *downloadedData;
@end
@implementation EOCNetworkFetcher
- (id)initWithURL:(NSURL*)url{
if((self = [super init])){
_url = url;
}
reutrn self;
}
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion{
self.completionHandler = completion;
// 开始请求网络数据,结束后调用p_requestCompleted方法
}
// 处理下载数据的私有方法
- (void)p_requestCompleted{
if(_completionHandler){
_completionHandler(_downloadedData);
}
}
@end
某个类可能创建网络数据获取器对象,并用其从URL中下载数据
@implementation EOCClass{
EOCNetworkFetcher *_networkFetcher;
NSData *_fetchData;
}
-(void)downloadData{
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;
}];
}
@end
上面代码就出现了保留环,由于completion handler块要设置EOCClass对象的实例变量,所以它必须捕获self属性,也就是说,handler块保留了创建网络数据获取器的EOCClass对象。而EOCClass又通过strong实例变量保留了获取器,最后获取器对象又保留了handler块,形成了保留环。
要打破保留环可以令_networkFetcher不再引用获取器,或者获取器的completionHandler属性不再持有handler块。例如,可以这样修改:
[_networkFetcher startWithCompletionHandler:^(NSData *data){
NSLog(@"Request URL %@ finished", _networkFetcher.url);
_fetchData = data;
// 令_networkFetcher不再引用获取器
_networkFetcher = nil;
}];
但这样做的缺点是,必须等completion handler运行后才能打破环,如果completion handler一直不运行,那么保留环就会一直存在,于是内存就会泄漏。
如果用另一种写法,EOCClass不再保留获取器:
-(void)downloadData{
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;
}];
}
@end
这样不会出现之前的保留环,但又会出现了新的保留环。completion handler需要通过获取器对象来引用其中的URL,于是块要保留获取器,而获取器又经由completionHandler属性保留了块。
要打破这个保留环可以令获取器一旦运行过completion handler后就不再保留它。
- (void)p_requestCompleted{
if(_completionHandler){
_completionHandler(_downloadedData);
}
self.completionHandler = nil;
}
网友评论