Objc高级编程之Block-读书笔记
Block存储域
Block也是Objc对象,其类型有以下几种:
- _NSConcreteStatckBlock
- _NSConcreteGlobalBlock
- _NSConcreteMallocBlock
目前ARC已经不再创建_NSConcreteStatckBlock类型的Block对象,进一步确保了block在使用时候的有效性,不过本书的内容我还是如实记录。_NSConcreteStatckBlock类的对象存储在栈(Stack)上,_NSConcreteGlobalBlock类的对象存储在数据区(.data)上,_NSConcreteMallocBlock类的对象存储在堆(Heap)上。

因为_NSConcreteGlobalBlock类型的block内部不依赖执行时的状态,所以能够用结构体存储在与全局变量相同的数据区域中。在整个程序周期内都可以安全地使用。但是在栈上_NSConcreteStatckBlock类的block,如果其所在作用域结束后就会被销毁,__block变量也一样。

Apple提供了将block和__block变量从栈复制到堆的方法解决了这个问题,这样即使block的作用域结束,我们还是可以继续安全地访问堆上的block。

通过block结构体成员变量__forwarding的帮助,__block变量无论在栈上还是在堆上都能够被正确地访问(后面详细说明)。
typedef int (^blk_t)(int);
blk_t func(int rate) {
return ^(int count) {return rate * count;};
}
//等价于
typedef int (^blk_t)(int);
blk_t func(int rate) {
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
//而objc_retainBlock实际上就是_Block_copy
大多数情况下编译器会适当地判断block的内存管理,不过少数情况还是需要开发者手动管理。比如向方法的参数传递block时,编译器就不能判断是否对block进行copy。这主要以来方法内部的实现是否自己对作为参数的block进行copy,对于copy过的方法,使用时就不用再对block手动copy。相反,就需要考虑手动复制(当然,目前已经没有_NSConcreteStatckBlock类的block存在也不存在这样的问题了)。
- (id)getBlockArray {
int val = 10;
return @[[^{NSLog(@"blk0:%d", val);} copy], [^{NSLog(@"blk1:%d", val);} copy]];
}
不管block之前存储在哪,用copy复制都不会引起任何问题,如果不确定时最好copy。

__block变量存储域
当block从栈copy到堆时,其使用的所有__block变量也全部从栈复制到堆。如果block已经在堆上,对block再次copy也不会对__block变量有任何影响。

多个block使用同一个__block变量时,任何一个block先从栈copy到堆后,__block变量也被copy到堆上并被持有,当其他block再copy到堆上时,仅仅增加__block变量的引用计数。

block从堆中被销毁时,其使用的__block变量也会被销毁。block的内存管理和objc对象的内存管理完全相同。

不管__block变量存储在栈上还是堆上,都能够正确地访问对应的变量。所有栈中的__block变量,将其架构体内部的forwarding值从自己替换为堆上的__block变量,这样无论栈上__block还是堆上__block都是forwarding指向同一个堆上结构体。

截获对象
blk_t blk;
{
id array = [[]NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
作用域结束后block调用正常,意味着变量array其超出作用域而存在并没有被销毁。
//编译器模拟代码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) {
_Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
}
static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
_Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
}
static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
}__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id __strong array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
id __strong array = __cself->array;
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
}
blk_t blk;
{
id __strong array = [[NSMutableArray alloc] init];
blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, array, 0x22000000);
blk = [blk copy];
}
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
作用域的局部自动变量array赋值给了block结构体中的成员变量,按照之前的描述,C语言结构体不能含有strong修饰符变量,因为编译器不知道应该何时对C语言结构体进行初始化和销毁从而管理内存,但是objc的runtime能够准确把握block从栈copy到堆以及从堆中销毁的时机,因此能够自动管理block结构体的初始化和销毁时机。

具体表现如下:
- 调用block的copy方法时
- block作为方法的返回值返回时
- 将block赋值给类的__strong修饰符id类型成员变量
- 方法名中含有usingBlock的Cocoa框架方法和GCD方法中传递block作为参数时
实现该功能需要在block描述结构体(XX_block_desc_X)中添加copy和dispose方法。在main_block_copy_0方法里,**_Block_object_assign相当于retain,持有对象并赋值给block实现结构体(XX_block_impl_X)中的成员变量。在main_block_dispose_0方法里,_Block_object_dispose**相当于release,释放赋值在block实现结构体中的成员变量。其中,参数BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF用语区分对象类型和__block类型。
__block变量和对象
__block id obj = [[NSObject alloc] init];
//编译器模拟代码
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 * *) ((char*)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变量虽然是个结构体,但实际上也是一个objc对象,并有持有和释放方法,其中使用_Block_object_assign持有__block对象,使用_Block_object_dispose释放__block对象。__block对象可以为__strong、__weak和__unsafe_unretained,不过使用__unsafe_unretained时注意出现野指针访问无效对象的情况,而__autoreleasing与__block编译时不通过。
copy/release
由于block时C语言的扩展,所以在C语言中也可以使用block语法。此时,使用Block_copy和Block_release代替objc的copy和release方法即可。另外,ARC无效时,__block修饰符被用来避免block中的循环引用。因为block从栈copy到堆时,__block成员变量如果是对象类型,则block不会retain它,反之则retain它。这样来避免block的循环引用。由于ARC和MRC时,__block修饰符的用途有很大区别,因为在开发时,必须要知道编译环境是ARC还是MRC,这一点需要特别注意。