当前位置: 首页 > news >正文

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的深入研究]]

相关文章:

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&#xff0…...

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 组件库 …...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...