Block的实质

什么是Block?

  • Block是将函数及其上下文封装起来的对象
    从源码来分析一下,通过clang命令行工具中的-rewrite-objc参数,我们可以把OC代码转化为C++的实现
1
2
3
4
5
6
7
int main()
{
void (^block)(void) = ^(void)
{printf("pp\n");};
block();
return 0;
}

MCBlock类中有这么一段代码,通过指令

1
clang -rewrite-objc MCBlock.m

得到一个转换后的.cpp文件源代码,上万行代码,我们要看的是下面这些代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct __block_impl {
voidvoid *isa;
int Flags;
int Reserved;
voidvoid *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(voidvoid *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) {
printf("pp\n");}
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()
{
void (*block)(void) = ((void (*)())&__main_block_impl_0((voidvoid *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}

源码解析

block实际的结构体部分

1
2
3
4
5
6
7
8
9
10
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(voidvoid *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

impl和Desc也是两个结构体,而_ main block_ imp_0就是该结构体的构造函数,用来初始化

第一个成员变量impl

1
2
3
4
5
6
struct __block_impl {
voidvoid *isa;
int Flags;
int Reserved;
voidvoid *FuncPtr;
};
  • isa 指针,这个很熟悉吧,这就能看出block其实就是对象.

  • flags和reserved这两个基本就是一些标记,不需要去了解

  • FuncPtr这个就是函数指针,也就是block所需要执行的代码段,真正存的地址

第二个成员变量 Desc

1
2
3
4
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}

reserve和Block_size分别代表了版本升级所需的区域和Block的大小

第三个结构体的构造函数

1
2
3
4
5
6
__main_block_impl_0(voidvoid *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}

Main函数中调用的基本转换

1
2
void (*block)(void) = ((void (*)())&__main_block_impl_0((voidvoid *)__main_block_func_0, &__main_block_desc_0_DATA));

分开倆步来看就是这样

1
2
3
4
5
6
7
/* 调用结构体函数初始化
struct __main_block_impl_0 impBlock = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
/* 赋值给该结构体类型指针变量
struct __main_block_impl_0 *block = &impBlock;
//就是把栈上生成的__main_block_impl_0的结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量block

细看下初始化函数内部的实现 _ block main_ impl_0

1
__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);

第一个参数是由Block块语法转换的正真内部函数指针,第二个参数就是作为静态全局变量初始化__main_block_desc_0的结构体实例指针,初始化如下

1
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

最终,初始化内部进行赋值

1
2
3
4
5
6
7
8
9
isa = &_NSConcreteStackBlock
Flags = 0
Reversed = 0
FuncPtr = __main_blcok_func_0(就是Block块代码转换成的C语言函数指针)
Desc = &__ main_block_desc_0_DATA

block()调用的内部实现

1
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

去掉类型就是
(*block->imp.FuncPtr)(block);

1
2
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("pp\n");}

最后就是直接调用函数指针进行最终的调用,由上面所描述的,FuncPtr就是由__main_block_func_0的函数指针所赋值的指针,而且可以看出,这个函数的参数正是指向block本身结构体实例,block作为参数进行了传递

总结

  • __main_block_impl_0这个就是Block内部的结构体(该结构体成员有impl,Desc 和一个该结构体的初始化函数)

  • Block块内的代码转换成了main_block_func_0的c语言函数(参数就是main_block_impl_0结构体)

  • Block语法的初始化实际就是将__main_blcok_impl_0的结构体实例化,重点是Block的代码块{}通过转换成C的函数指针进行结构体的成员变量FuncPtr指针赋值

  • Block调用就是通过实例化的结构体里面的FuncPtr指针就行函数调用,而且参数就是该结构体本身