oc block详解
我们知道在Block使用中,Block内部能够读取外部局部变量的值。但我们需要改变这个变量的值时,我们需要给它附加上__block修饰符。__block另外一个比较多的使用场景是,为了避免某些情况下Block循环引用的问题,我们也可以给相应对象加上__block 修饰符。今天为大家详细讲解Block实现的详细过程
- block的实际结构
- block的类型
- block是如何捕捉变量的
- 不同类型的block的复制
- ARC中block的工作
- 外部变量的复制
block的实际结构
下面是block的数据结构定义
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
通过该图,我们可以知道,一个 block 实例实际上由 6 部分构成:
1. isa 指针,所有对象都有该指针,用于实现对象相关的功能。
2. flags,用于按 bit 位表示一些 block 的附加信息。
3. reserved,保留变量。
4. invoke,函数指针,指向具体的 block 实现的函数调用地址。
5. descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
6. variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
了解block数据结构后,我们接下来对block进行底层解析,首先看一段最简单的block代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
^{ };
}
return 0;
}
利用clang编译器把这段代码编译成c++的实现,我们会获得一个.cpp文件,打开它,可以从它最底部获得如下代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
(void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
}
return 0;
}
不难看出其中的__main_block_impl_0
就是block的一个C++的实现(最后面的_0代表是main中的第几个block),整个结构和之前的所讲的结构是完全吻合的。在定义__main_block_impl_0
结构体时,同时创建了__main_block_func_0
和__main_block_desc_0
的对象__main_block_desc_0_DATA
,__main_block_func_0
为block实现函数(因为block为空,所以实现函数也为空),__main_block_desc_0_DATA
为block的附加描述信息。并且在main函数中利用__main_block_impl_0
的构造函数将上面两个参数传入进去。这就是对一个简单的block块的底层解析。
block的类型
block的常见类型有3种:
- _NSConcreteGlobalBlock(全局)
- _NSConcreteStackBlock(栈)
- _NSConcreteMallocBlock(堆)
附上UNIX的进程虚拟内存段分布图:
- _NSConcreteStackBlock 保存在栈中的block,出栈时会被销毁
- _NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量
- _NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁
注意:分配在全局变量上的Block,在变量作用域外也可以通过指针安全的访问。但分配在栈上的Block,如果它所属的变量作用域结束,该Block就被废弃。同样地,__block变量也分配在栈上,当超过该变量的作用域时,该__block变量也会被废弃。
这时候_NSConcreteMallocBlock就显得格外重要了。Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题。将分配到栈上的Block复制到堆上,这样但栈上的Block超过它原本作用域时,堆上的Block还可以继续存在。由于block的复制最终都会调用_Block_copy_internal函数,所以观察这个函数就可以知道堆中block是如何被创建的了:
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
...
aBlock = (struct Block_layout *)arg;
...
// Its a stack block. Make a copy.
if (!isGC) {
// 申请block的堆内存
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 拷贝栈中block到刚申请的堆内存中
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 改变isa指向_NSConcreteMallocBlock,即堆block类型
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
else {
...
}
}
从以上代码以及注释可以很清楚的看出,函数通过memmove
将栈中的block的内容拷贝到了堆中,并使isa指向了_NSConcreteMallocBlock
。
block主要的一些学问就出在栈中block向堆中block的转移过程中了。
block是如何捕捉变量的
我们通过编译转换捕捉不同类型的block,以对他们进行区别。
局部变量
前:
- (void)test
{
int a;
^{a;};
}
后:
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
int a;
// a(_a)是构造函数的参数列表初始化形式,相当于a = _a。从_I_Person_test看,传入的就是a
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
a;}