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 组件库 …...
Akagi雀魂AI辅助工具:从麻将新手到高手的完整指南
Akagi雀魂AI辅助工具:从麻将新手到高手的完整指南 【免费下载链接】Akagi 支持雀魂、天鳳、麻雀一番街、天月麻將,能夠使用自定義的AI模型實時分析對局並給出建議,內建Mortal AI作為示例。 Supports Majsoul, Tenhou, Riichi City, Amatsuki,…...
App加固后崩溃、卡顿、无法上架?详解性能与兼容性问题的根因与解法
“加固后应用闪退频发”,“在部分低端机型上启动变慢”,“华为应用商店审核未通过,提示加固异常”。这些是很多开发者在初次引入应用加固后遭遇的真实困境。技术加固的本意是保护应用,但如果因此牺牲了用户体验和上架通道…...
Amlogic S9xxx Armbian终极指南:让电视盒子变身全能服务器
Amlogic S9xxx Armbian终极指南:让电视盒子变身全能服务器 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w, s905, s905l, rk35…...
OBS StreamFX插件完整指南:免费打造专业直播画面的终极方案
OBS StreamFX插件完整指南:免费打造专业直播画面的终极方案 【免费下载链接】obs-StreamFX StreamFX is a plugin for OBS Studio which adds many new effects, filters, sources, transitions and encoders! Be it 3D Transform, Blur, complex Masking, or even …...
如何简单快速地获取网盘直链下载?这款免费开源工具给你完整解决方案
如何简单快速地获取网盘直链下载?这款免费开源工具给你完整解决方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移…...
FigmaCN:3步告别英文界面困扰,让设计效率提升50%
FigmaCN:3步告别英文界面困扰,让设计效率提升50% 【免费下载链接】figmaCN 中文 Figma 插件,设计师人工翻译校验 项目地址: https://gitcode.com/gh_mirrors/fi/figmaCN 还在为Figma的英文界面而分心吗?每次看到"Fram…...
代码补全已进入“语义理解临界点”?——SITS2026核心论文深度拆解(含LLM+AST+IDE三栈协同架构图)
第一章:SITS2026深度解读:代码补全技术演进 2026奇点智能技术大会(https://ml-summit.org) SITS2026(Smart Intelligent Text Synthesis Summit 2026)首次系统性地将代码补全技术划分为三代范式:基于规则的模板填充、…...
Windows Cleaner:高效解决C盘爆红的最佳实践指南
Windows Cleaner:高效解决C盘爆红的最佳实践指南 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服! 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner 还在为C盘空间不足而烦恼吗?每次看到红色…...
Zstats高级版教程(4):如何进行变量统计描述(下)—针对定量变量
本篇是风暴统计平台教程系列的第四章,将详细说明如何使用统计描述模块,查看变量分布。因为涉及内容比较多,分为上下两篇,此为上篇前面我们已经介绍了风暴统计平台Zstats高级版针对分类变量如何开展统计描述的使用教程。Zstats高级…...
pymongo,一个灵活的 Python 库!
【pymongo,一个灵活的 Python 库!】在日常数字化生活中,我们产生的用户信息、聊天记录、文章内容、设备数据、订单日志等信息,大多具有结构不固定、字段灵活、嵌套层级多的特点,传统关系型数据库难以高效存储和查询。而…...
