iOS——Block two
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. `// 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的原因了
- 成员变量
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 实际函数指针 FuncPtr,FuncPtr 指针指向 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 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 构造函数的时候,传入了两个参数。
- 第一个参数:
__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; // 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;}
}
整段代码主要分成三个逻辑分支
- 通过
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;
}
- 是否是全局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的深入研究]]
相关文章:
iOS——Block two
Block 的实质究竟是什么呢?类型?变量?还是什么黑科技? Blocks 是 带有局部变量的匿名函数 Blocks 由 OC 转 C 源码方法 在项目中添加 blocks.m 文件,并写好 block 的相关代码。打开「终端」,执行 cd XX…...
Ubuntu出现内部错误解决办法
使用的Ubuntu版本是18.04,使用的时候弹出对话框说出现了内部错误,好奇是哪里出现了错误,查找了一下解决的办法,记录一下。 参考解决方案:ubantu出现了内部错误 一旦程序崩溃过一次,就会生成一个.crash文件…...
2023年中职组“网络安全”赛项吉安市竞赛任务书
2023年中职组“网络安全”赛项 吉安市竞赛任务书 一、竞赛时间 总计:360分钟 竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略配置 A-3 流量完整性保护 A-4 事件监控 A-5 服务加固…...
ELK日志分析系统介绍及搭建(超详细)
目录 一、ELK日志分析系统简介 二、Elasticsearch介绍 2.1Elasticsearch概述 三、Logstash介绍 四、Kibana介绍 五、ELK工作原理 六、部署ELK日志分析系统 6.1ELK Elasticsearch 集群部署(在Node1、Node2节点上操作) 6.2部署 Elasticsearch 软件 …...
docker 资源限制
目录 1、CPU使用率 2、CPU共享比例 3、CPU周期限制 4、CPU核心限制 5、CPU 配额控制参数的混合案例 6、内存限制 7、Block IO 的限制 8、限制bps 和iops docker资源限制 Docker容器技术底层是通过Cgroup(Control Group 控制组)实现容器对物理资…...
HCIP 交换综合实验--企业三层架构
题目 1、内网IP地址使用172.16.0.0/26分配 2、SW1和SW2之间互为备份 3、VRRP/STP/VLAN/Eth-trunk均使用 4、所有PC均通过DHCP获取IP地址 5、ISP只能配置IP地址 6、所有电脑可以正常访问ISP路由器环回 实验步骤 第一步、规划IP地址 R1-R2:100.1.1.0/24 R2-LSW1…...
微服务的基础使用
微服务 Maven的依赖冲突解决方案: 路径最短原则 配置优先原则 破坏规则则使用排除 SpringBoot场景启动器starter的开发流程 c3p0-spring-boot-starter自定义场景启动器 test-c3p0调用自定义场景启动器 SpringBoot自动装配 SpringBoot应用启动原理 nacos服务治…...
opencv-29 Otsu 处理(图像分割)
Otsu 处理 Otsu 处理是一种用于图像分割的方法,旨在自动找到一个阈值,将图像分成两个类别:前景和背景。这种方法最初由日本学者大津展之(Nobuyuki Otsu)在 1979 年提出 在 Otsu 处理中,我们通过最小化类别内…...
网络中通过IP地址查找位置
display ip routing-table 查看路由表 display vlan 查看vlan 信息 display stp brief 查看生成树信息 display mac-address 查看mac 地址表 display arp 查看arp表 SW1 SW2...
MyBatis的动态SQL语句
文章目录 前言LocalDate数据库代码po 包 ifwhere 标签 查trim 标签 增set 标签 改foreach 标签 删 前言 提示:这里可以添加本文要记录的大概内容: 查询条件是动态的 MyBatis的动态SQL语句是指在运行时根据不同条件选择不同的SQL语句执行。 这些条件可…...
交互式AI技术与模型部署:bert-base-chinese模型交互式问答界面设置
使用Gradio实现Question Answering交互式问答界面,首先你需要有一个已经训练好的Question Answering模型,这里你提到要使用bert-base-chinese模型。 Gradio支持PyTorch和TensorFlow模型,所以你需要将bert-base-chinese模型转换成PyTorch或Te…...
Edge浏览器安装vue devtools
1. 下载地址 GitHub - vuejs/devtools: ⚙️ Browser devtools extension for debugging Vue.js applications. 2. 下载后的压缩包解压并打开文件夹,右键选择:git bush here 3. 安装依赖 npm install 4. 成功安装依赖后打包 npm run build...
zookeeper基础
安装 https://archive.apache.org/dist/zookeeper/zookeeper-3.5.7/ 命令 bin/zkServer.sh start bin/zkServer.sh stop bin/zkServer.sh status bin/zkCli.sh ll / quit 各个配置项的含义: tickTime:每个时钟周期的毫秒数。ZooKeeper使用一个内部…...
【C++】类与对象(2)
文章目录 前言一、类的6个默认成员函数二、构造函数1.概念2.特性3.初始化列表 三、析构函数1.概念2.特性 四、拷贝构造函数1.概念2.特性 五、赋值运算符重载1.运算符重载2.赋值运算符重载3.前置和后置重载 六、取地址及const取地址操作符重载总结 前言 在前面,给大…...
数据结构——绪论
一、绪论 (一)基本概念 数据:数据是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。 数据元素:数据元素是数据的基本单位,在计算机程序中通常作为一个整…...
Docker Dockerfile 语法与指令
一、简介 Docker 镜像原理、容器转成镜像 随便找个案例,进入 https://hub.docker.com/ 搜索 centos,然后随便找个版本(例如:centos7)点击一下,就会进入 centos7 的 dockerfile 文件: // 空镜像…...
【LeetCode每日一题】——566.重塑矩阵
文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 矩阵 二【题目难度】 简单 三【题目编号】 566.重塑矩阵 四【题目描述】 在 MATLAB 中&…...
Manim(一款强大的数学可视化动画引擎)学习历程
相逢情便深,恨不相逢早 第一眼看见上面这种类型的视频我就深深被它的简约清楚所折服,我觉得它完全符合我的审美,我也相信只要了解过制作这种视频的软件的人都会喜欢上它。运用这种风格比较有名的是b站里的一位up主名叫3Blue1Brown࿰…...
powershell脚本写一个托盘图标
1、准备ico格式图标 star_bethlehem_icon 文件名改为star.ico 2、安装VSCode 如何下载安装VSCode 扩展:PowerShell扩展 3、创建项目 1、运行PowerShell命令 mkdir trayicon_ps1;cd trayicon_ps1;New-Item trayicon.ps1;code .2、将star.ico放入trayicon_ps1文…...
前端Vue入门-day08-vant组件库
(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 vant 组件库 安装 导入 全部导入 按需导入 浏览器配饰 Viewport 布局 Rem 布局适配 vant 组件库 …...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
