曾静的博客

但行好事,莫问前程.

嗨,我是曾静 (@devzeng),目前暂居深圳。


这是我用来记录平日学习笔记的地方,欢迎您的访问.

iOS中ARC下block的循环引用

在iOS4.2时,Apple推出ARC的内存管理机制。这是一种编译期的内存管理方式,在编译期间,编译器会判断对象的使用情况,并适当的加上retain和release,使得对象的内存被合理的管理。所以,从本质上说ARC和MRC在本质上是一样的,都是通过引用计数的内存管理方式。

使用ARC虽然可以简化内存管理,但是ARC并不是万能的,有些情况程序为了能够正常运行,会隐式地持有或者复制对象,如果不加以注意,便会造成内存泄露。在ARC下,当block获取到外部变量时,由于编译器无法预测获取到的变量何时会被突然释放,为了保证程序能够正确运行,让block持有获取到的变量。

本文主要通过一个例子来介绍在ARC情况下使用Block不当会导致的内存泄露的问题。

###示例代码

示例代码来源于《Effective Objective-C 2.0》(编写高质量iOS与OS X代码的52个有效方法)。

(1)EOCNetworkFetcher.h

typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);

@interface EOCNetworkFetcher : NSObject

@property (nonatomic, strong, readonly) NSURL *url;

- (id)initWithURL:(NSURL *)url;

- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;

@end

(2)EOCNetworkFetcher.m

@interface EOCNetworkFetcher ()

@property (nonatomic, strong, readwrite) NSURL *url;
@property (nonatomic, copy) EOCNetworkFetcherCompletionHandler completionHandler;
@property (nonatomic, strong) NSData *downloadData;

@end

@implementation EOCNetworkFetcher

- (id)initWithURL:(NSURL *)url {
    if(self = [super init]) {
        _url = url;
    }
    return self;
}

- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion {
    self.completionHandler = completion;
    //开始网络请求
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        _downloadData = [[NSData alloc] initWithContentsOfURL:_url];
        dispatch_async(dispatch_get_main_queue(), ^{
        	 //网络请求完成
            [self p_requestCompleted];
        });
    });
}

- (void)p_requestCompleted {
    if(_completionHandler) {
        _completionHandler(_downloadData);
    }
}

@end

(3)EOCClass.m

@implementation EOCClass {
    EOCNetworkFetcher *_networkFetcher;
    NSData *_fetchedData;
}

- (void)downloadData {
    NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
    _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData *data) {
        _fetchedData = data;
    }];
}
@end

代码分析:

1、completion handler块因为要设置_fetchedData实例变量的值,所以它必须捕获self变量,也就是说handler块保留了EOCClass实例;

2、而EOCClass实例通过strong实例变量保留了EOCNetworkFetcher,最后EOCNetworkFetcher实例对象又保留了handler块。

引用关系如下下图所示:

arc_block_retain_cycle.png

###解决办法

要想打破保留环

1、方法一:使用完EOCNetworkFetcher对象之后就没有必要在保留该对象了,在block里面将对象释放即可打破保留环。

- (void)downloadData {
    NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
    _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData *data) {
        _fetchedData = data;
        _networkFetcher = nil;//加上此行,此处是为了打破循环引用
    }];
}

2、方法二:上面的方法需要调用者自己来将对象手动设置为nil,对于使用者来说会造成很多困恼,如果忘记将对象设置为nil就会造成循环引用。在运行完completion handler之后将block释放即可。

- (void)p_requestCompleted {
    if(_completionHandler) {
        _completionHandler(_downloadData);
    }
    self.completionHandler = nil;//加上此行,此处是为了打破循环引用
}

3、方法三:将引用的一方变成weak,从而避免循环引用。

- (void)downloadData {
   __weak __typeof(self) weakSelf = self;
   NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
   _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
   [_networkFetcher startWithCompletionHandler:^(NSData *data) {
   		//如果想防止 weakSelf 被释放,可以再次强引用
    	__typeof(&*weakSelf) strongSelf = weakSelf;
    	if (strongSelf) {
    		strongSelf.fetchedData = data;
    	}
   }];
}

###参考资料

1、《ARC 下内存泄露的那些点》

2、《Weakself的一种写法》

3、《[iOS]ARC下循环引用的问题》

4、《Effective Objective-C 2.0》(Tips:40)、《iOS开发进阶》(P190)

最近的文章

iOS8中的Safari Action Extension

扩展(Extension)是iOS8中引入的一个非常重要的新特性。扩展让app之间的数据交互成为可能。用户可以在app中使用其他应用提供的功能,而无需离开当前的应用。前面介绍过关于Today Widget的使用,本文介绍Action Extension在iOS8中的开发。###创建Action Extension1、使用Xcode6创建一个iOS工程,菜单栏File->New->Target出现下面的可选项,选择Action Extension:2、配置Extension的Pr...…

iOS继续阅读
更早的文章

在iOS开发中使用自定义字体

在iOS的项目开发中经常遇到需要使用一些自定义的字体文件,比如仿宋_GB2312、方正小标宋_GBK等。之前我们为了使用这些自定义的字体,在应用的资源包中放入这些字体文件。因为字体文件通常比较大,有的一个字库就达到10M以上(拿方正小标宋_GBK这个字库来说就有13M之多),这样打包后的ipa文件的体积就可能会变得很大,对于只有个别的模块需要特殊的字体样式的应用来说很不划算,那么在iOS6.0以后苹果就开放了动态加载字体的权限。下面就iOS中使用字体的这两种方式进行介绍。###使用静态字体...…

iOS继续阅读