block引发的陷阱
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的具体区别的话,可查阅其它资料.