iOS——Block two

Block  的实质究竟是什么呢?类型?变量?还是什么黑科技?
 Blocks 是 带有局部变量的匿名函数

Blocks 由 OC 转 C++ 源码方法

  1. 在项目中添加 blocks.m 文件,并写好 block 的相关代码。
  2. 打开「终端」,执行 cd XXX/XXX 命令,其中 XXX/XXX 为 block.m 所在的目录。
  3. 继续执行clang -rewrite-objc block.m
  4. 执行完命令之后,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. `// impl:Block 的实际函数指针,指向包含 Block 主体部分的 __main_block_func_0 结构体`
12.     `struct __block_impl impl;`
13.     `// Desc:Desc 指针,指向包含 Block 附加信息的 __main_block_desc_0() 结构体`
14.     `struct __main_block_desc_0* Desc;`
15.     `// __main_block_impl_0:Block 构造函数`
16.     `__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {`
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.     `// impl:Block 的实际函数指针,指向包含 Block 主体部分的 __main_block_func_0 结构体`
4.     `struct __block_impl impl;`
5.     `// Desc:Desc 指针,指向包含 Block 附加信息的 __main_block_desc_0() 结构体`
6.     `struct __main_block_desc_0* Desc;`
7.     `// __main_block_impl_0:Block 构造函数`
8. `__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {`
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的原因了

  1. 成员变量 impl;
  2. 成员变量 Desc 指针;
  3. __main_block_impl_0 构造函数。
  4. 析构函数中所需要的函数:fp传递了具体的block实现__main_block_func_0,然后保存在block结构体的impl

block捕获变量

这就说明了block声明只是将block实现保存起来,具体的函数实现需要自行调用
值得注意的是,当block为堆block时,block的构造函数会多出来一个参数a,并且在block结构体中多出一个属性a
Pasted image 20230725160455.png

接着把目光转向__main_block_func_0实现

  • __cself__main_block_impl_0的指针,即block本身
  • int a = __cself->aint a = block->a
  • 由于a只是个属性,所以是堆block只是值拷贝(值相同,内存地址不同)
  • 这也是为什么捕获的外界变量不能直接进行操作的原因,如a++会报错

当__block修饰外界变量的时候


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q6SlRlhS-1690945388106)(https://raw.githubusercontent.com/ArnoVD97/PhotoBed/master/photo202307281627162.png)]
__block修饰的属性在底层会生成响应的结构体,保存原始变量的指针,并传递一个指针地址给block——因此是指针拷贝
Pasted image 20230726093723.png
源码中增加了一个名为_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 的相关附加信息:

  1. 今后版本升级所需区域大小: reserved 变量。
  2. 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 flags=0) {`
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 构造函数的时候,传入了两个参数。

  1. 第一个参数:__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"); }

  1. 第二个参数:__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;      // Desc:Desc 指针`
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;}
}

整段代码主要分成三个逻辑分支

  1. 通过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_value+2, where)) {return old_value+2;}}
}

这里可能有个疑问
为什么引用计数是 +2 而不是 +1 ?
因为flags的第一号位置已经存储着释放标记

else if (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock;
}
  1. 是否是全局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;
}
  1. 栈block -> 堆block的过程
  • 先通过malloc在堆区开辟一片空间
  • 再通过memmove将数据从栈区拷贝到堆区
  • invokeflags同时进行修改
  • block的isa标记成_NSConcreteMallocBlock
    [[__block的深入研究]]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/54704.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣 62. 不同路径

题目来源:https://leetcode.cn/problems/unique-paths/ C题解1:动态规划。声明二维数组。 确定dp数组(dp table)以及下标的含义。dp[i][j] :表示从(0 ,0)出发,到(i, j) …

Vue 自定义事件绑定与解绑

绑定自定义事件 说到 Vue 自定义事件,那就需要搞清楚一个问题,为啥有这个玩意。 说到自定义事件之前,需要理解 组件基础的概念。理解了基础概念之后,我们就知道 Vue 的父子之间的通信, 一是 父组件通过 Prop 向子组件…

动态规划之树形DP

动态规划之树形DP 树形DP何为树形DP 树形DP例题HDU-1520 Anniversary partyHDU-2196 Computer834. 树中距离之和 树形DP 何为树形DP 树形DP是指在“树”这种数据结构上进行的动态规划:给出一颗树,要求以最少的代价(或取得最大收益&#xff…

1、Spark SQL 概述

1、Spark SQL 概述 Spark SQL概念 Spark SQL is Apache Spark’s module for working with structured data. 它是spark中用于处理结构化数据的一个模块 Spark SQL历史 Hive是目前大数据领域,事实上的数据仓库标准。 Shark:shark底层使用spark的基于…

针对高可靠性和高性能优化的1200V碳化硅沟道MOSFET

目录 标题:1200V SiC Trench-MOSFET Optimized for High Reliability and High Performance摘要信息解释研究了什么文章创新点文章的研究方法文章的结论 标题:1200V SiC Trench-MOSFET Optimized for High Reliability and High Performance 摘要 本文详…

Jenkins工具系列 —— 插件 钉钉发送消息

文章目录 安装插件 Ding TalkJenkins 配置钉钉机器人钉钉APP配置项目中启动钉钉通知功能 安装插件 Ding Talk 点击 左侧的 Manage Jenkins —> Plugins ——> 左侧的 Available plugins Jenkins 配置钉钉机器人 点击 左侧的 Manage Jenkins ,拉到最后 钉…

《数据同步-NIFI系列》Nifi配置UpdateAttribute实现字符串时间戳转日期

Nifi配置UpdateAttribute实现字符串时间戳转日期 数据处理流程如下:查询源数据库,将Avro转为Json格式,然后使用EvaluateJsonPath修改字段名,最后使用replaceText将参数组成SQL,最后PutSQL。 一、字段串时间戳导致无法插…

嵌入式开发学习(STC51-13-温度传感器)

内容 通过DS18B20温度传感器,在数码管显示检测到的温度值; DS18B20介绍 简介 DS18B20是由DALLAS半导体公司推出的一种的“一线总线(单总线)”接口的温度传感器; 与传统的热敏电阻等测温元件相比,它是一…

java中javamail发送带附件的邮件实现方法

java中javamail发送带附件的邮件实现方法 本文实例讲述了java中javamail发送带附件的邮件实现方法。分享给大家供大家参考。具体分析如下: JavaMail,顾名思义,提供给开发者处理电子邮件相关的编程接口。它是Sun发布的用来处理email的API。它…

vim、awk、tail、grep的使用

vim命令 $定位到光标所在行的行末^定位到光标所在行的行首gg定位到文件的首行G定位到文件的末行dd删除光标所在行ndd删除n行(从光标所在行开始)D删除光标所在行,使之变为空白行x删除光标所在位置字符nx删除n个字符,从光标开始向后…

【linux源码学习】【实验篇】使用bochs运行linux0.11系统(搭建一个自己的工作站)

目录 背景资源获取bochs环境搭建windowsbochs环境搭建linux声明 背景 最近看赵炯老师的《linux内核完全注释》,然后在最后一个习题里面看到使用bochs跑一下0.11的内核代码,本来觉得很难,但是如果做过一遍就会发现其实很简单,这个…

No112.精选前端面试题,享受每天的挑战和学习

文章目录 说一说JavaScript有几种方法判断变量的类型?说一说defer和async区别?HTTP(超文本传输协议)是什么?说一下浏览器输入URL发生了什么?一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青…