【IOS学习】之9、Blocks的实现再续

【IOS学习】之九、Blocks的实现再续
__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变量声明部分
__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~~~
【IOS学习】之9、Blocks的实现再续
【IOS学习】之9、Blocks的实现再续
为了避免循环引用,可声明附有__weak修饰符的变量,并将self赋值使用。
id __weak tmp = self;
blk_ = ^{NSLog(@"self = %@", tmp);};

此时,由于Block存在时,持有该Block的MyObject类对象即赋值在变量tmp中的self必定存在,因此不需要判断变量tmp的值是否为nil。
【IOS学习】之9、Blocks的实现再续
【IOS学习】之9、Blocks的实现再续
下面代码也会引起循环:
@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,便会循环引用并引起内存泄露。
如图:
【IOS学习】之9、Blocks的实现再续
【IOS学习】之9、Blocks的实现再续
MyObject类对象持有Block
Block持有__block变量
__block变量持有MyObject类对象。


通过执行execBlock实例方法,Block被执行,nil被赋值给__block变量tmp中。
因此__block变量tmp堆MyObject类对象的强引用失效。 
如何避免?:
【IOS学习】之9、Blocks的实现再续
【IOS学习】之9、Blocks的实现再续
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