探寻Block的本质(3)—— 基础类型的变量捕获markdown
探寻Block的本质(5)—— 对象类型的变量捕获iphone
探寻Block的本质(6)—— __block的深刻分析函数
上一篇,咱们初步认识了Block的一些基本知识。如今,咱们来一块儿挖掘一下Block的底层结构。 首先仍是新建一个命令行项目
定义一个最最简单的blockpost
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//Block的定义
void (^block)(void) = ^(){
NSLog(@"I am a block!");
};
//Block的调用
block();
}
return 0;
}
复制代码
紧接着,经过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
命令拿到编译后的c++文件,添加到项目中。直接查看该文件的尾部 在main函数里面,有不少强制类型转换代码,为了便于理解,去掉这些转换代码(不影响原有的逻辑),将其main函数简化成以下ui
//【1.2】
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//C++语法下,下面的函数是一个类的构造方法,函数名与类名相同,与oc里面的init方法相似,返回结构对象
__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;
}
};
//【1.1】这个函数是block内部封装的代码块执行逻辑
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_main_65b8ed_mi_0);
}
//【1.3】
static struct __main_block_desc_0 {
size_t reserved;//保留变量,暂无用处
size_t Block_size;//block的大小
}__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
//main函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//【1】-------------- 【1.4】
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA));
//【2】
block->FuncPtr(block);
}
return 0;
}
复制代码
下面来分析一下系统底层的执行的逻辑。 首先,根据oc代码中main
函数里面的第一句代码spa
//Block的定义
void (^block)(void) = ^(){
NSLog(@"I am a block!");
};
复制代码
系统会进行步骤【1.1】
,也就是会吧Block中的代码块封装到函数static void __main_block_func_0(struct __main_block_impl_0 *__cself){}
里面.命令行
接下来的步骤【1.2】
,系统会定义结构体struct __main_block_impl_0
做为Block的底层数据结构,因为是C++语法下的结构体,所以这个结构体里面能够定义函数,代码中的函数为这个结构体的构造函数__main_block_impl_0()
3d
接下来的【1.3】
步骤,系统又定义告终构体static struct __main_block_desc_0
,而且生成了一个实例__main_block_desc_0_DATA
,用来存放Block的相关描述信息。
最后是【1.4】
步骤,系统利用构造函数__main_block_impl_0()
生成的对象的地址赋值给block
指针。传入的参数有两个,分别为 (1)__main_block_func_0
__main_block_impl_0
->impl
->funcPtr
(2)&__main_block_desc_0_DATA
static struct __main_block_desc_0
构造这样步骤【1】
就结束了。再上一幅图来呈现一下整个过程
接下来根据根据咱们的OC代码
//【2】
block->FuncPtr(block);
复制代码
系统会经过步骤【2】
完成了block的调用,也就是调用block内部封装的那个函数指针。 block->FuncPtr(block);
⚠️小细节
block->FuncPtr(block);
为何没报错?由于按照咱们的代码结构,应该是block->impl->FuncPtr(block);
才对。 且看struct __main_block_impl_0
中的第一个成员为struct __block_impl impl;
,说明impl
是一个结构体,不是指针,因此能够直接把它的内容搬到struct __main_block_impl_0
来理解所以
block->FuncPtr(block);
等价于block->impl->FuncPtr(block);
,两种方式实际上均可以拿到FuncPtr
进行调用。
至此,Block的底层最简单数据结构就初步分析完了。