首页 > 技术文章 > block引发的陷阱

dashunzi 2014-06-04 20:29 原文

  block在项目的开发中使用时非常频繁的,苹果官方也极力推荐使用block。其实,究其本质,block就是指向结构体的指针(可利用运行时机制查看底层生成的c代码)。然而在使用block时会存在很多陷阱(主要是内存泄露),这些都是必须要注意的。接下来举个简单的实例:

 假设一个类,拥有两个属性:block和name;

 1 //
 2 //  SZBlocTest.h
 3 //  block引发的内存泄漏
 4 //
 5 //  Created by mac on 14-6-17.
 6 //  Copyright (c) 2014年 shunzi. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 
11 @interface SZBlocTest : NSObject
12 
13 @property (nonatomic, copy) void(^block)();
14 
15 @property (nonatomic, copy) NSString *name;
16 
17 @end

  在这之前,先解释下block为什么使用copy来修饰?其主要原因如下:

  在普通情况下,任何block,都是存在于栈中,这也意味着,其生命周期由系统管理,不需要我们手动管理.这就存在一个问题,那就是我们如果使用block保存一段代码块,欢乐的等待被回调的时候,说不定在之前就已经被系统回收了!

  那么,如何让block存储到堆中呢?方法就是使用copy来修饰,做一次复制.(如果使用retain的话,只会将其计数器加一,多做一次强引用,但不会重新分配新的内存,所以,依然存在于栈中).

  此时,产生第二个问题:如果在block中调用持有它的对象(有点绕,理解下),就会产生循环引用,造成内存泄漏!例如:

 1 //
 2 //  SZBlocTest.m
 3 //  block引发的内存泄漏
 4 //
 5 //  Created by mac on 14-6-17.
 6 //  Copyright (c) 2014年 shunzi. All rights reserved.
 7 //
 8 #import "SZBlocTest.h"
 9 
10 @implementation SZBlocTest
11 - (id)init
12 {
13     if (self = [super init]) {
14         self.block = ^{
15             NSLog(@"%@",_name);
16             NSLog(@"%@",self->_name);
17             NSLog(@"%@",self.name);
18         };
19     }
20     return self;
21 }
22 @end

  以上代码中,block中使用的

15             NSLog(@"%@",_name);
16             NSLog(@"%@",self->_name);
17             NSLog(@"%@",self.name);

  都会引发循环引用,其中15与16行,是等同的;为什么会引发循环引用呢?
  因为,block存在于堆中,在其代码块中引用的对象都会产生一个強指针.而这时候问题就产生了,因为block本身就被其引用的对象(copy)强指针指向着.这样就造成了双方都无法释放,从而造成了内存泄漏.

解决方案如下:
 1 - (id)init
 2 {
 3     if (self = [super init]) {
 4         __unsafe_unretained SZBlocTest *temp = self;
 5 //        __weak SZBlocTest *temp = self;
 6         self.block = ^{
 7             NSLog(@"%@",temp->_name);
 8             NSLog(@"%@",temp.name);
 9         };
10     }
11     return self;
12 }

  这时候,很多细心的人就会注意到,第五行的被注释的代码

 __weak SZBlocTest *temp = self;
和上面的
__unsafe_unretained SZBlocTest *temp = self;

 有什么区别呢?

 简单说就是,unsafe,为什么不安全呢?因为当其所修饰的对象释放时,它并不知道,所以,其地址依然存在,不会清为nil,这样第七行代码

temp->_name
是可以访问的(在使用时也应当注意这个细节),而使用weak修饰的话,当其修饰的对象被释放时,temp会被清为nil,这样就会出现nil->_name的情况.这是不允许的.
如果想要了解
__unsafe_unretained和__weak的具体区别的话,可查阅其它资料.



推荐阅读