首先我们看一个最简单的Block的实现代码:
void blockMain() {
void(^MyBlock)(void) = ^{
printf("block test");
};
MyBlock();
}
通过clang -rewrite-objc block.c后会得到一个block.cpp的文件,里面就是转换后的代码:
struct __blockMain_block_impl_0 {
struct __block_impl impl;
struct __blockMain_block_desc_0* Desc;
__blockMain_block_impl_0(void *fp, struct __blockMain_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们可以看到,转换后的block __blockMain_block_impl_0 = 函数名 + block_impl + block的顺序,里面有两个变量,变量的类型为两个结构体,如下:
struct __block_impl {
void *isa; // block指向block所属的类型(_NSConcreteGlobalBlock, _NSConcreteStackBlock, _NSConcreteMallocBlock)
int Flags;
int Reserved;
void *FuncPtr; // 回调的函数指针
};
static struct __blockMain_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockMain_block_desc_0_DATA = { 0, sizeof(struct __blockMain_block_impl_0)};
最后看blockMain:
static void __blockMain_block_func_0(struct __blockMain_block_impl_0 *__cself) {
printf("block test");
}
void blockMain() {
void(*MyBlock)(void) = ((void (*)())&__blockMain_block_impl_0((void *)__blockMain_block_func_0, &__blockMain_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);
}
struct __blockMain_block_impl_0 {
struct __block_impl impl;
struct __blockMain_block_desc_0* Desc;
int a;
int b;
__blockMain_block_impl_0(void *fp, struct __blockMain_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockMain_block_func_0(struct __blockMain_block_impl_0 *__cself) {
// __cself指向block的指针
int a = __cself->a; // bound by copy
int b = __cself->b; // bound by copy
int c = a + b;
printf("%d", c);
}
首先,C++中,函数名后面带上冒号是它特有的赋值方式,所以,: a(_a), b(_b)表示给a与b赋值。与不带参数的block相比,带参数的block其实也就是多了几个参数,即对参数进行了复制。
添加__block修饰符的变量,在编译后会多出:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
结构,其中__forwarding指向自身。因此在block内部就是通过这个指针,来修改block外部的变量。
如下:
static void __blockMain_block_func_0(struct __blockMain_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 2;
printf("%d", (a->__forwarding->a));
}
所以看完编译后的代码,我们就能够知道block的运行逻辑了。
那么为什么会需要一个forwarding指针指向自身呢?看下面这个例子:
int main(int argc, const char * argv[]) {
__block int val = 0;
void (^blk)(void) = [^{ ++val; } copy];
++val;
blk();
printf("val = %d", val);
return 0;
}
我们有一个__block修饰的变量val,那么根据blk中我们知道,这个block会被复制到堆上,而对应的val生成的结构__Block_byref_val_0的值也会被复制到堆上去,那么在栈上的val该怎么跟堆上的做到统一呢?那么这个时候我们就设计了,将栈上的val结构体的forwarding指针的值指向栈上的该对象。通过编译后的代码,我们也能看出,如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
++(val->__forwarding->val); }
++(val.__forwarding->val);