__block变量和对象
__block id obj = [];
__block id __strong obj = [];
上述两行代码是等同的,ARC有效的时候,id类型以及对象类型变量必定会附加所有权修饰符,缺省为附有__strong修饰符。
看一下clang转换的代码:
//__block变量用结构体部分
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
__strong id obj;
};
static void __Block_byref_id_object_copy_131(void* dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void**)((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void**)((vhar*)src + 40), 131);
}
__Block_byref_obj_0 obj = {
0,
&obj,
0x2000000,
sizeof(__Block_byref_obj_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
[[NSObject alloc] init]
};
当Block从栈赋值到堆时,使用_Block_object_assign函数,持有Block截获的对象。 当堆上的Block被废弃时,使用_Block_object_dispose函数,释放Block截获的对象。
只有__strong怎能没有__weak呢?
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
id _weak array2 = array;
blk = [^(id obj){
[array2 addObject:obj];
NSLog(@"array2 count = %d", [array2 count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
此时执行结果都是0.
这是因为 附有__strong修饰符的变量array在该变量作用域结束的同时被释放、废弃,nil被赋值在附有__weak修饰符的变量array2中。
如下同时__block __weak 双修饰符:
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
__block id _weak array2 = array;
blk = [^(id obj){
[array2 addObject:obj];
NSLog(@"array2 count = %d", [array2 count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
执行结果与之前相同。
因为即使附加了__block说明符,附有__strong修饰符的变量array也会在该变量作用域结束的同时非释放废弃,nil被赋值给附有__weak修饰符的变量array2中。
还有就是__autorelease 与 __block一起使用会发生编译错误。
Block循环引用
当我们在Block使用__strong修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block所持有。 这样容易引起循环引用:
typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init {
self = [super init];
blk_ = ^{NSLog(@"self = %@", self);};
return self;
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main() {
id o = [[MyObject alloc] init];
NSLog(@"%@", o);
return 0;
}
代码中,dealloc一定未被调用。
MyObject类对象的Block类型成员变量blk_持有赋值为Block的强引用。 即MyObject类对象持有Block。
init实例方法中执行的Block语法使用附有__strong修饰符的id类型变量self。 并且由于Block语法赋值在了成员变量blk_中, 因此通过Block语法生成在栈上的Block此时由栈复制到堆。并持有所使用的self。 self持有Block,Block持有self~~~
为了避免循环引用,可声明附有__weak修饰符的变量,并将self赋值使用。
id __weak tmp = self;
blk_ = ^{NSLog(@"self = %@", tmp);};
此时,由于Block存在时,持有该Block的MyObject类对象即赋值在变量tmp中的self必定存在,因此不需要判断变量tmp的值是否为nil。
下面代码也会引起循环:
@interface MyObject:NSObject {
blk_t blk_;
id obj_;
}
@end
@implementation MyObject
- (id) init {
self = [super init];
blk_ = ^{NSLog(@"obj_ = %@", obj_);};
return self;
}
@end
Block中没有self,也同样截获了self,引起循环。
其实在block语法中使用了obj_,其实就已经截获了self: self->obj_。
与前面一样,我们可以声明__weak的临时变量来避免循环引用。
我们还可以使用__block变量来避免循环引用:
typedef void (^blk_t)(void);
@interface MyObject:NSObject{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init {
self = [super init];
__block id tmp = self;
blk = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
};
return self;
}
- (void)execBlock{
blk_();
}
- (void)dealloc{
NSLog(@"dealloc");
}
@end
int main() {
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
这里并没有引起循环引用,但是如果不调用execBlock实例方法,即不执行赋值给成员变量blk_的Block,便会循环引用并引起内存泄露。
如图:
MyObject类对象持有Block
Block持有__block变量
__block变量持有MyObject类对象。
通过执行execBlock实例方法,Block被执行,nil被赋值给__block变量tmp中。
因此__block变量tmp堆MyObject类对象的强引用失效。
如何避免?:
MyObject类对象持有Block
Block持有__block变量。
比较一下 使用__block变量避免循环引用的方法和使用__weak修饰符及__unsafe_unretained修饰符避免循环引用:
__block优点:
1、通过__block变量可控制对象的持有时间
2、在不能使用__weak修饰符的环境中不使用__unsafe_unretained修饰符即可
使用__block变量的缺点:
为避免循环引用必须执行Block。
但是当执行了Block语法,没有执行Block路径的时候,不能避免循环引用。 如果由于Block印发了循环引用时,根据Block的用途选择使用__block变量、__weak修饰符或者__unsafe_unretained修饰符来避免循环引用。
但是当ARC无效时?
此时我们需要手动将Block从栈复制到堆,并且要释放Block。 此时要使用copy和release。
在arc无效的时候,__block说明符被用来避免Block中的循环引用。这是由于当Block从栈赋值到堆时,若Block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain。 若Block使用的变量为没有__block说明符的id类型或对象类型的自动变量,就会被retain。
下列代码在arc有效或者无效都会导致循环引用:
typedef void (^blk_t)(void);
@interface MyObject:NSObject{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init{
self = [super init];
blk_ = ^{NSLog(@"self = %@", self);};
return self;
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main() {
id o = [[MyObject alloc] init];
NSLog(@"%@", o);
return 0;
}
此时我们使用__block变量就可以避免:
__block id tmp = self;
blk_ = ^{NSLog(@"self = %@", tmp);};
————2014/3/22 Beijing