跟建设通差不多额网站,如何申请公众号,网站建设期末答案,行业网站建设分析Block 的实质究竟是什么呢#xff1f;类型#xff1f;变量#xff1f;还是什么黑科技#xff1f; Blocks 是 带有局部变量的匿名函数
Blocks 由 OC 转 C 源码方法
在项目中添加 blocks.m 文件#xff0c;并写好 block 的相关代码。打开「终端」#xff0c;执行 cd XX…Block 的实质究竟是什么呢类型变量还是什么黑科技 Blocks 是 带有局部变量的匿名函数
Blocks 由 OC 转 C 源码方法
在项目中添加 blocks.m 文件并写好 block 的相关代码。打开「终端」执行 cd XXX/XXX 命令其中 XXX/XXX 为 block.m 所在的目录。继续执行clang -rewrite-objc block.m执行完命令之后block.m 所在目录下就会生成一个 block.cpp 文件这就是我们需要的 block 相关的 C 源码。
1. /* 包含 Block 实际函数指针的结构体 */
2. struct __block_impl {
3. void *isa;
4. int Flags;
5. int Reserved; // 今后版本升级所需的区域大小
6. void *FuncPtr; // 函数指针
7. };
9. /* Block 结构体 */
10. struct __main_block_impl_0 {
11. // implBlock 的实际函数指针指向包含 Block 主体部分的 __main_block_func_0 结构体
12. struct __block_impl impl;
13. // DescDesc 指针指向包含 Block 附加信息的 __main_block_desc_0 结构体
14. struct __main_block_desc_0* Desc;
15. // __main_block_impl_0Block 构造函数
16. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags0) {
17. impl.isa _NSConcreteStackBlock;
18. impl.Flags flags;
19. impl.FuncPtr fp;
20. Desc desc;
21. }
22. };23. /* Block 主体部分结构体 */
24. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
25. printf(myBlock\n);
26. }27. /* Block 附加信息结构体包含今后版本升级所需区域大小Block 的大小*/
28. static struct __main_block_desc_0 {
29. size_t reserved; // 今后版本升级所需区域大小
30. size_t Block_size; // Block 大小
31. } __main_block_desc_0_DATA { 0, sizeof(struct __main_block_impl_0)};32. /* main 函数 */
33. int main () {
34. void (*myBlock)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA));
35. ((void (*)(__block_impl *))((__block_impl *)myBlock)-FuncPtr)((__block_impl *)myBlock);36. return 0;
37. }Block 结构体
我们先来看看 __main_block_impl_0 结构体 Block 结构体
1. /* Block 结构体 */
2. struct __main_block_impl_0 {
3. // implBlock 的实际函数指针指向包含 Block 主体部分的 __main_block_func_0 结构体
4. struct __block_impl impl;
5. // DescDesc 指针指向包含 Block 附加信息的 __main_block_desc_0 结构体
6. struct __main_block_desc_0* Desc;
7. // __main_block_impl_0Block 构造函数
8. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags0) {
9. impl.isa _NSConcreteStackBlock;
10. impl.Flags flags;
11. impl.FuncPtr fp;
12. Desc desc;
13. }
14. };从上边我们可以看出__main_block_impl_0 结构体Block 结构体包含了三个部分 从上述代码可以看出block的本质是个 __main_block_impl_0 的结构体对象这就是为什么能用 % 打印出block的原因了 成员变量 impl;成员变量 Desc 指针;__main_block_impl_0 构造函数。析构函数中所需要的函数fp传递了具体的block实现__main_block_func_0然后保存在block结构体的impl中
block捕获变量
这就说明了block声明只是将block实现保存起来具体的函数实现需要自行调用 值得注意的是当block为堆block时block的构造函数会多出来一个参数a并且在block结构体中多出一个属性a
接着把目光转向__main_block_func_0实现
__cself是__main_block_impl_0的指针即block本身int a __cself-a即int a block-a由于a只是个属性所以是堆block只是值拷贝值相同内存地址不同这也是为什么捕获的外界变量不能直接进行操作的原因如a会报错
当__block修饰外界变量的时候 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q6SlRlhS-1690945388106)(https://raw.githubusercontent.com/ArnoVD97/PhotoBed/master/photo202307281627162.png)] __block修饰的属性在底层会生成响应的结构体保存原始变量的指针并传递一个指针地址给block——因此是指针拷贝 源码中增加了一个名为_Block_byref_a_0的结构体用来保存我们要capture并且修改的变量i __main_block_impl_0引用的是_Block_byref_a_0结构体指针起到修改外部变量的作用 _ Block_byref_a_0里面有isa也是一个对象 我们需要负责_Block_byref_a_0结构体相关的内存管理所以_main_block_desc_0中增加了copy和dispose的函数指针用于在抵用前后修改相应变量的引用计数
struct __block_impl impl 说明
第一部分 impl 是 __block_impl 结构体类型的成员变量。__block_impl 包含了 Block 实际函数指针 FuncPtrFuncPtr 指针指向 Block 的主体部分也就是 Block 对应 OC 代码中的 ^{ printf(myBlock\n); }; 部分。还包含了标志位 Flags今后版本升级所需的区域大小 Reserved__block_impl 结构体的实例指针 isa。
1. /* 包含 Block 实际函数指针的结构体 */
2. struct __block_impl {
3. void *isa; // 用于保存 Block 结构体的实例指针
4. int Flags; // 标志位
5. int Reserved; // 今后版本升级所需的区域大小
6. void *FuncPtr; // 函数指针
7. };struct __main_block_desc_0* Desc 说明
第二部分 Desc 是指向的是 __main_block_desc_0 类型的结构体的指针型成员变量__main_block_desc_0 结构体用来描述该 Block 的相关附加信息
今后版本升级所需区域大小 reserved 变量。Block 大小Block_size 变量。
1. /* Block 附加信息结构体包含今后版本升级所需区域大小Block 的大小*/
2. static struct __main_block_desc_0 {
3. size_t reserved; // 今后版本升级所需区域大小
4. size_t Block_size; // Block 大小
5. } __main_block_desc_0_DATA { 0, sizeof(struct __main_block_impl_0)};__main_block_impl_0 构造函数说明
第三部分是 __main_block_impl_0 结构体Block 结构体 的构造函数负责初始化 __main_block_impl_0 结构体Block 结构体 的成员变量。
1. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags0) {
2. impl.isa _NSConcreteStackBlock;
3. impl.Flags flags;
4. impl.FuncPtr fp;
5. Desc desc;
6. }关于结构体构造函数中对各个成员变量的赋值我们需要先来看看 main() 函数中对该构造函数的调用。
void (*myBlock)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA));我们可以把上面的代码稍微转换一下去掉不同类型之间的转换使之简洁一点
1. struct __main_block_impl_0 temp __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA);
2. struct __main_block_impl_0 myBlock temp;这样就容易看懂了。该代码将通过 __main_block_impl_0 构造函数生成的 __main_block_impl_0 结构体Block 结构体类型实例的指针赋值给 __main_block_impl_0 结构体Block 结构体类型的指针变量 myBlock。
可以看到 调用 __main_block_impl_0 构造函数的时候传入了两个参数。
第一个参数__main_block_func_0。 - 其实就是 Block 对应的主体部分可以看到下面关于 __main_block_func_0 结构体的定义 和 OC 代码中 ^{ printf(myBlock\n); }; 部分具有相同的表达式。 - 这里参数中的 __cself 是指向 Block 的值的指针变量相当于 OC 中的 self。
c /* Block 主体部分结构体 */ static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf(myBlock\n); }
第二个参数__main_block_desc_0_DATA__main_block_desc_0_DATA 包含该 Block 的相关信息。 我们再来结合之前的 __main_block_impl_0 结构体定义。 - __main_block_impl_0 结构体Block 结构体可以表述为
1. struct __main_block_impl_0 {
2. void *isa; // 用于保存 Block 结构体的实例指针
3. int Flags; // 标志位
4. int Reserved; // 今后版本升级所需的区域大小
5. void *FuncPtr; // 函数指针
6. struct __main_block_desc_0* Desc; // DescDesc 指针
7. };- __main_block_impl_0 构造函数可以表述为
1. impl.isa _NSConcreteStackBlock; // isa 保存 Block 结构体实例
2. impl.Flags 0; // 标志位赋值
3. impl.FuncPtr __main_block_func_0; // FuncPtr 保存 Block 结构体的主体部分
4. Desc __main_block_desc_0_DATA; // Desc 保存 Block 结构体的附加信息[[Block签名]] __main_block_impl_0 结构体Block 结构体相当于 Objective-C 类对象的结构体isa 指针保存的是所属类的结构体的实例的指针。_NSConcreteStackBlock 相当于 Block 的结构体实例。对象 impl.isa _NSConcreteStackBlock; 语句中将 Block 结构体的指针赋值给其成员变量 isa相当于 Block 结构体的成员变量 保存了 Block 结构体的指针这里和 Objective-C 中的对象处理方式是一致的。 也就是说明 Block 的实质就是对象。 block的copy分析
接下来就来研究下栈block转换成到堆block的过程——_Block_copy
void *_Block_copy(const void *arg) {struct Block_layout *aBlock;if (!arg) return NULL;// The following would be better done as a switch statementaBlock (struct Block_layout *)arg;if (aBlock-flags BLOCK_NEEDS_FREE) {// latches on highlatching_incr_int(aBlock-flags);return aBlock;}else if (aBlock-flags BLOCK_IS_GLOBAL) {return aBlock;}else {// Its a stack block. Make a copy.struct Block_layout *result (struct Block_layout *)malloc(aBlock-descriptor-size);if (!result) return NULL;memmove(result, aBlock, aBlock-descriptor-size); // bitcopy first
#if __has_feature(ptrauth_calls)// Resign the invoke pointer as it uses address authentication.result-invoke aBlock-invoke;
#endif// reset refcountresult-flags ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not neededresult-flags | BLOCK_NEEDS_FREE | 2; // logical refcount 1_Block_call_copy_helper(result, aBlock);// Set isa last so memory analysis tools see a fully-initialized object.result-isa _NSConcreteMallocBlock;return result;}
}整段代码主要分成三个逻辑分支
通过flags标识位——存储引用计数的值是否有效
block的引用计数不受runtime处理的是由自己管理的
static int32_t latching_incr_int(volatile int32_t *where) {while (1) {int32_t old_value *where;if ((old_value BLOCK_REFCOUNT_MASK) BLOCK_REFCOUNT_MASK) {return BLOCK_REFCOUNT_MASK;}if (OSAtomicCompareAndSwapInt(old_value, old_value2, where)) {return old_value2;}}
}这里可能有个疑问 为什么引用计数是 2 而不是 1 因为flags的第一号位置已经存储着释放标记
else if (aBlock-flags BLOCK_IS_GLOBAL) {return aBlock;
}
是否是全局block——
else {// Its a stack block. Make a copy.size_t size Block_size(aBlock);struct Block_layout *result (struct Block_layout *)malloc(size);// 开辟堆空间if (!result) return NULL;memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)// Resign the invoke pointer as it uses address authentication.result-invoke aBlock-invoke;#if __has_feature(ptrauth_signed_block_descriptors)if (aBlock-flags BLOCK_SMALL_DESCRIPTOR) {uintptr_t oldDesc ptrauth_blend_discriminator(aBlock-descriptor,_Block_descriptor_ptrauth_discriminator);uintptr_t newDesc ptrauth_blend_discriminator(result-descriptor,_Block_descriptor_ptrauth_discriminator);result-descriptor ptrauth_auth_and_resign(aBlock-descriptor,ptrauth_key_asda, oldDesc,ptrauth_key_asda, newDesc);}
#endif
#endif// reset refcountresult-flags ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not neededresult-flags | BLOCK_NEEDS_FREE | 2; // logical refcount 1_Block_call_copy_helper(result, aBlock);// Set isa last so memory analysis tools see a fully-initialized object.result-isa _NSConcreteMallocBlock;return result;
}
栈block - 堆block的过程
先通过malloc在堆区开辟一片空间再通过memmove将数据从栈区拷贝到堆区invoke、flags同时进行修改block的isa标记成_NSConcreteMallocBlock [[__block的深入研究]]