美文网首页
第40条:用块引用其所属对象时不要出现保留环

第40条:用块引用其所属对象时不要出现保留环

作者: MrSYLong | 来源:发表于2018-10-14 21:55 被阅读3次

    使用块时,若不仔细思量,很容易导致"保留环"。

    // 网络数据获取器,提供了一套从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;
    }
    

    相关文章

      网友评论

          本文标题:第40条:用块引用其所属对象时不要出现保留环

          本文链接:https://www.haomeiwen.com/subject/mpntzftx.html