Block的实质
Block的定义是带有自动变量的匿名函数,下面从源码的角度探究下Block究竟是什么
下面是一个Block的简单实现:
int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... void (^blk)(void) = ^{ printf("Block\n"); }; blk(); } return 0; }
通过clang编译器转换为如下的C++代码:
// 定义Block的实现结构体 struct __block_impl { void *isa; // 指向所属类的指针 int Flags; // 标志性参数,暂时没用到所以默认为0 int Reserved; // 今后版本升级所需的区域大小。 void *FuncPtr; // 函数指针,指向实际执行的函数,也就是Block中花括号里面的代码内容。 }; // 定义具体的Block结构体 struct __main_block_impl_0 { struct __block_impl impl; // 上面定义的Block实现结构体的变量 struct __main_block_desc_0* Desc; // Block描述符的指针 // 构造函数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) { impl.isa = &_NSConcreteStackBlock; // 设置Block的类型标识 impl.Flags = flags; // 设置标志 impl.FuncPtr = fp; // 设置Block的执行函数指针 Desc = desc; // 设置Block的描述符 } }; // Block的实际执行函数 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Block\n"); // Block执行时输出的内容 } // Block描述符结构体 static struct __main_block_desc_0 { size_t reserved; // 今后版本升级所需区域的大小(一般填0) size_t Block_size; // Block的大小 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; // 初始化Block描述符 // 主函数 int main(int argc, const char * argv[]) { // 创建Block void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); // 调用Block ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); return 0; }
可以看到^{printf("Block\n");
转换成了
// Block的实际执行函数 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Block\n"); // Block执行时输出的内容 }
block的匿名函数被转换成普通C语言函数,函数命名按照block语法所属的函数名(此处为main函数)和在该函数中出现的顺序(此处为0)。
这里的_ self相当于OC中的指向对象自身的self,也就是_ self指向block值的变量
_ self
参数是__main_block_impl_0
的参数
__main_block_impl_0
// 定义具体的Block结构体 struct __main_block_impl_0 { struct __block_impl impl; // 上面定义的Block实现结构体的变量 struct __main_block_desc_0* Desc; // Block描述符的指针 // 构造函数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) { impl.isa = &_NSConcreteStackBlock; // 设置Block的类型标识 impl.Flags = flags; // 设置标志 impl.FuncPtr = fp; // 设置Block的执行函数指针 Desc = desc; // 设置Block的描述符 } };
这里主要定义类两个结构体,第一个成员是__block_impl
结构体,第二个是__main_block_desc_0
,和实现构造函数
__block_impl
struct __block_impl { void *isa;//指向所属类的指针 int Flags;//标志性参数,暂时没用到所以默认为0 int Reserved;//今后版本升级所需的区域大小。 void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。 };
如果block变量为空的话,这里的FuncPtr指针就会指向错误的地址而不是实际执行的函数,此时就会报错。
__main_block_desc_0
static struct __main_block_desc_0 { size_t reserved; //今后版本升级所需区域的大小(一般填0) size_t Block_size; //Block的大小 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
第一个成员变量指的是今后版本升级所需区域的大小(一般填0)。
第二个成员变量是Block的大小。
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};:
这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是__main_block_desc_0_DATA。
其中reserved为0,Block_size是sizeof(struct __main_block_impl_0)。
__main_block_impl_0()构造函数
__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; }
在这个结构体的构造函数里,isa指针保持这所属类的结构体的实例的指针。_mainblockimlp0结构体就相当于Objective-C类对象的结构体,这里的_NSConcreteStackBlock相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现。
main函数
// 主函数 int main(int argc, const char * argv[]) { // 创建Block // 使用类型转换将Block结构体的地址转换成函数指针类型 void (*blk)(void) = // 将Block结构体转换为函数指针类型 ((void (*)())&__main_block_impl_0( // Block的执行函数指针 (void *)__main_block_func_0, // Block描述符的地址 &__main_block_desc_0_DATA )); // 调用Block // 获取Block结构体中的FuncPtr成员,并进行必要的类型转换后调用 ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); return 0; }
第一部分是将__main_block_impl_0
结构体类型的自动变量,也就是栈上生成的__main_block_impl_0
结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk。
第二部分是相当于源代码中的blk(),即使用该Block部分。去掉转换部分就是:
(*blk->impl.FuncPtr)(blk);
使用函数指针调用函数。由Block语法转换的__main_block_impl_0函数的指针被赋值成员变量FunPtr中。
Block捕获机制
block变量捕获就是在执行Block语法的时候,Block语法表达式所使用的自动变量的值是被保存进了Block的结构体实例中,也就是Block自身中。
如果Block外面还有很多自动变量,静态变,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
下面是对于不同变量时Block的捕获:
- 全局变量: 不捕获
- 局部变量: 捕获值
- 静态全局变量: 不捕获
- 静态局部变量: 捕获指针
- const修饰的局部常量:捕获值
- const修饰的静态局部常量:捕获指针
当Block捕获的是变量的值时,在block内部不能修改变量,当Block捕获的是变量的指针时,在block内部可以修改变量
对于全局变量Block是进行直接访问的
全局变量:
int max = 3; int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... void (^blk)(void) = ^{ printf("%d\n", max); }; max = 60; blk(); } return 0; }
局部变量:
int dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (^blk)(void) = ^{ printf(fmt, val); }; val = 2; fmt = "These values were changed. val = %d\n"; blk();
静态全局变量
static int max = 3; int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... void (^blk)(void) = ^{ printf("%d\n", max); }; max = 60; blk(); } return 0; }
静态局部变量
static int val = 10; const char *fmt = "val = %d\n"; void (^blk)(void) = ^{ val = 2; printf(fmt, val); }; fmt = "These values were changed. val = %d\n"; blk();
Block捕获普通变量
int main(int argc, const char * argv[]) { int dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (^blk)(void) = ^{ printf(fmt, val); }; blk(); return 0; }
转化后的代码:
// Block结构体定义 struct __block_impl { void *isa; // 指向Block类型的标识 int Flags; // Block标志位 int Reserved; // 保留字段 void *FuncPtr; // 指向Block执行函数的指针 }; // 定义特定于main函数的Block实现 struct __main_block_impl_0 { struct __block_impl impl; // 包含Block的基本实现 struct __main_block_desc_0* Desc; // 指向Block描述符 const char *fmt; // 格式字符串 int val; // 值 // 构造函数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) { // 初始化成员变量 impl.isa = &_NSConcreteStackBlock; // 设置Block类型标识 impl.Flags = flags; // 设置Block标志位 impl.FuncPtr = fp; // 设置Block执行函数的指针 Desc = desc; // 设置Block描述符 } }; // Block执行函数 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char *fmt = __cself->fmt; // 获取格式字符串 int val = __cself->val; // 获取值 printf(fmt, val); // 执行打印操作 } // Block描述符 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) }; // 主函数 int main(int argc, const char * argv[]) { int dmy = 256; // 未使用的变量 int val = 10; // 用于输出的整数值 const char *fmt = "val = %d\n"; // 输出格式字符串 // 创建Block void (*blk)(void) = // 类型转换为函数指针 ((void (*)())&__main_block_impl_0( // Block执行函数指针 (void *)__main_block_func_0, // Block描述符地址 &__main_block_desc_0_DATA, // 格式字符串 fmt, // 用于输出的整数值 val )); return 0; }
与上次不同的是在block内部语法表达式中使用的自动变量(fmt,val)被作为成员变量追加到了_mainblockimpl0结构体中(注意:block没有使用的自动变量不会被追加,如dmy变量)。
struct __main_block_impl_0 { struct __block_impl impl; // 包含Block的基本实现 struct __main_block_desc_0* Desc; // 指向Block描述符 const char *fmt; // 格式字符串 int val; // 值 // 构造函数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) { // 初始化成员变量 impl.isa = &_NSConcreteStackBlock; // 设置Block类型标识 impl.Flags = flags; // 设置Block标志位 impl.FuncPtr = fp; // 设置Block执行函数的指针 Desc = desc; // 设置Block描述符 } };
在Block执行函数中,fmt,var都是从__cself里面获取的,更说明了二者是属于block的.
这两个变量是值传递,而不是指针传递,也就是说Block仅仅截获自动变量的值,所以这就解释了即使改变了外部的自动变量的值,也不会影响Block内部的值。
// Block执行函数 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char *fmt = __cself->fmt; // 获取格式字符串 int val = __cself->val; // 获取值 printf(fmt, val); // 执行打印操作 }
Block捕获_ _block修饰的变量
编译器会将_ _block修饰的变量包装成一个对象,变成对象后就可以根据指针地址在block内部去修改外部的变量,block通过**__forwarding**指针去修改变量的值
int main(int argc, const char * argv[]) { __block int val = 10; void (^blk)(void) = ^{ val = 1; }; return 0; }
转换后的代码:
//__block说明符修饰后的变量的结构体 struct __Block_byref_val_0 { void *__isa; //指向所属类的指针 __Block_byref_val_0 *__forwarding; //指向自己的内存地址的指针 int __flags; //标志性参数,暂时没用到所以默认为0 int __size; //该结构体所占用的大小 int val; //该结构体存储的值,即原变量的赋的值 }; //block本体 struct __main_block_impl_0 { struct __block_impl impl; //block的主体 struct __main block desc 0* Desc; //存储该block的大小 __Block_byref_val_0 *val; //__block修饰的变量的值 //构造函数 __main_block_impl_0(void *fp, struct __main_block_desc 0 *desc, __Block_byrefval_0 *_val, int flags=0) : val(_val->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }; //封装的block逻辑,存储了block的代码块 static void __main_block_func_0(struct__main_block_impl_0 *_cself) { __Block_byref_val_0 *val =__cself->val; (val->__forwarding->val) = 1; } static void_main_block_copy_0(struct __main_block_impl_0* dst, struct __main_block_impl_0* src) { //根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用 _Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF); } static void __main_block_dispose_0(struct __main_block_imp1_0* src) { //自动释放引用的auto变量(相当于release) _Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF); } static struct __main_block_desc_0 { unsigned long reserved; //保留字段 unsigned long Block_size; //block大小 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); //copy的函数指针,下面使用构造函数进行了初始化 void (*dispose)(struct __main_block_impl_0*); //dispose的函数指针,下面使用构造函数进行了初始化 } //构造函数,初始化保留字段、block大小及两个函数 __main_block_desc_0_DATA = { 0, sizeof(structmain_block_impl_0), __main_block_copy_O, __main_block_dispose_0 }; int main() { //之前的 __block int val = 10;变成了结构体实例 struct __Block_byref_val_0 val = { 0, //isa指针 &val, //指向自身地址的指针 0, //标志变量 sizeof(__Block_byref_val_0), //block大小 10 //该数据的值 }; blk = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000); return 0; }
通过源代码发现__block说明符修饰的变量的结构体__多了个指针_ _Block_byref_val_0 *__forwarding
,指向自己的内存地址的指针
struct __Block_byref_val_0 { void *__isa; //指向所属类的指针 __Block_byref_val_0 *__forwarding; //指向自己的内存地址的指针 int __flags; //标志性参数,暂时没用到所以默认为0 int __size; //该结构体所占用的大小 int val; //该结构体存储的值,即原变量的赋的值 };
在__main_block_func_0
打印的并不是_ _Block_byref_val_0* val而是(val->__forwarding->val)并且 _ _block说明符修饰的变量变成了一个结构体
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_val_0 *val = __cself->val; (val->__forwarding->val) = 1; printf("val = %d\n", (val->__forwarding->val)); }
那么为什么会有成员变量__forwarding呢?__
因为__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上时都能够正确的访问__block变量。
总结
- Block的本质是一个对象,其内部的第一个成员是isa指针,通过内部的FuncPtr指针指向实际执行的函数,也就是Block中花括号里面的代码内容。
- Block只会捕获自己需要使用的自动变量并将其保存进了Block的结构体实例中,也就是Block自身中。
- Block捕获普通的局部变量只是通过构造函数将它的值传递进了Block的结构体实例中的成员,因此不能在Block内部修改值,并且在Block外修改的值也不会影响Block捕获时的值
- Block捕获static修饰的局部变量是捕获它的指针,因此可以在Block内部修改值
- Block捕获_ _Block变量时会将其转换成结构体并且生成
_ _Block_byref_val_0 *__forwarding
指针,指向自己的内存地址的指针,通过该指针就能访问和修改 _ _Blcok变量