Block本质的探究
一、准备工作
- 1、创建一个
命令行
项目 - 2、Mac自带的终端
Terminal
进入创建好的项目,并在
mian.m
里面定义一个Block
, 如下所示:
int main(int argc, const char * argv[]) {
@autoreleasepool {
//定义一个block
void (^myBlock)(void) = ^{
NSLog(@"Hello Block!");
};
//block调用
myBlock();
}
return 0;
}
打开
Terminal
,cd
到当前项目main.m
所在目录,执行以下指令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
上面的指令目的是借助clang
编译main.m
得到编译后的文件-main.cpp
。
我把编译后的主要的代码贴出来,以便进行后面的探究。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
#pragma clang assume_nonnull end
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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_ab6d44_mi_0);
}
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 (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
二、定义Block
探究
由于我们是在main
函数里面定义的Block
,所以我们在编译后
的文件里也是对应从main
函数开始探究。
如果我们把代码对应起来的话就是下面这样的:
- 定义block,在编译前和编译后
编译前:
//定义一个block
void (^myBlock)(void) = ^{
NSLog(@"Hello Block!");
};
编译后:
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
编译后去除一些强制转换操作后:
void (*myBlock)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA));
经过上面的简化我们不难发现:
- 调用了
__main_block_impl_0 (参数1, 参数2)
,并把返回值的地址(&)
赋值给了myBlock
。- 2、参数1是:
__main_block_func_0
- 3、参数2是:
&__main_block_desc_0_DATA)
,把参数2的地址值(&)
传递进去了.
1、探究__main_block_impl_0
所以下一步,我们需要去看看__main_block_impl_0
函数是什么?
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//构造函数(类似OC的init方法,把外面传进来的参数赋值给自己的成员变量,并返回self),返回结构体对象
__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;
}
};
从编译后的文件里可以看到这个名为
__main_block_impl_0 的 c++结构体
,里面有:
- 一个
构造函数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)
- 两个主要的成员变量
struct __block_impl impl
和struct __main_block_desc_0* Desc
。
所以,我们不难得出,
Block其实是一个结构体对象
。
从外面传进来的参数赋值给了它的两个成员变量,所以下一步我们需要弄清楚,这两个成员变量是什么。
2、探究__block_impl
在编译后的文件中我们可以找到:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__block_impl
结构体里面包含的:
void *isa
, 结构体的地址void *FuncPtr
, 函数的地址int Flags
,结构体的标识int Reserved
,是一个保留字段
看到这里,我们就不难发现这个
Block的内存地址其实就是 __block_impl 的 isa所指向的地址
。
为什么这么说?看下面我们贴出的__main_block_impl_0
的结构体,这里就不再细说了
struct __main_block_impl_0 {
struct __block_impl impl;
...
}
3、探究__main_block_desc_0
在编译后的文件中我们可以找到:
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)};
size_t reserved
,保留字段默认值是0
size_t Block_size
,存储了Block的内存大小,通过sizeof(struct __main_block_impl_0)
计算出来的
也就是说,这个结构体主要是来存储Block的描述信息,如:内存大小等
。
4、看下_main_block_impl_0(...)
构造函数
现在清楚了Block的两个成员变量后,我们就来看看它的构造函数接收的参数。
_main_block_impl_0(
void *fp,
struct __main_block_desc_0 *desc,
int flags=0
)
它接收3个参数:
- 1.
void *fp
,在函数里面把它赋值给了impl.FuncPtr
。 - 2.
struct __main_block_desc_0 *desc
,在函数里面把它赋值给了Desc
. - 1.
int flags=0
,这个是有一个默认值,在上面的调用过程中没有传第三个参数,说明 当前情况下使用默认值就可以。
再来看下编译后这个构造函数接收的具体参数:
void (*myBlock)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA));
5、探究__main_block_func_0
__main_block_func_0
是block接收的第一个参数,我们可以在编译后的文件中找到
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_68_54lgxdz97sj4whg31nyj1d800000gn_T_main_ab6d44_mi_0);
}
__main_block_func_0就是Block封装了执行逻辑的函数
,现在我们所看到的内部封装的要执行的函数就是一开始写在Block里面的输出函数:NSLog(@"Hello Block!");
所以第一个参数:
把封装了要执行函数的函数地址传给了__main_block_impl_0, 里面把函数地址赋值给了 impl.FuncPtr = fp;
6、探究__main_block_desc_0_DATA
__main_block_desc_0_DATA
是block接收的第二个参数,我们可以在编译后的文件中找到:
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)};
从上面可以看出:
-
__main_block_desc_0_DATA
的0
传给了__main_block_desc_0
的size_t reserved
. -
__main_block_desc_0_DATA
的sizeof(struct __main_block_impl_0)
传给了__main_block_desc_0
的size_t Block_size
.
所以第二个参数, 实际上是计算了这个
Block的内存大小
,并把得到的这个结构体的地址值传递进去。
到此,结束了定义一个Block的本质的探究。
三、Block
调用的
首先,我们回看一下调用的代码:
//block调用
myBlock();
编译后
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
编译后去除强制转换就变成如下的代码
myBlock->FuncPtr(myBlock);
上面的分析我们知道:
myBlock
是__main_block_impl_0
构造函数创建完后返回的指针地址myBlock->FuncPtr
这句话的作用:
通过myBlock
的地址拿到impl
,再通过impl
拿到里面的FuncPtr
保存的地址值,然后再调用方法- 传进去
(myBlock)
的地址就是传给了封装要执行函数的函数, 即 static void __main_block_func_0(struct __main_block_impl_0 *__cself)
所以到此也就完成了本次对Block本质的探究!
四、总结一下
-
1、
Block
本质上也是一个OC对象
,内部也有一个isa
指针。 -
2、
Block
是封装了函数调用和函数调用环境的OC对象
-
3、
Block
内部的两个主要成员:struct __block_impl impl
,保存了Block的内存地址,封装要执行函数的函数地址等
struct __main_block_desc_0* Desc
,内部主要保存了Block的内存大小
。