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.rhkb.cn/news/76440.html

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

相关文章

LNMP安装

目录 1、LNMP简述: 1.1、概述 1.2、LNMP是一个缩写词,及每个字母的含义 1.3、编译安装与yum安装差异 1.4、编译安装的优点 2、通过LNMP创建论坛 2.1、 安装nginx服务 2.1.1、关闭防火墙 2.1.2、创建运行用户 2.1.3、 编译安装 2.1.4、 优化路…

前端JS实用操作符,一些骚操作✨

目录 0、!! 双重逻辑非操作符 📚 1、?? 操作符 空值合并/空判断 ✅ 2、?. 可选链运算符🔍 3、?? 操作符 逻辑空值赋值运算符 💚 4、三元运算符 📗 5、~~ 操作符 双位运算符 🔨 6、&&与 ||或 短…

【Autolayout自动布局介绍 Objective-C语言】

一、好,我们开始介绍Autolayout 1.什么事Autolayout 好,那么,接下来,我们介绍一下这个Autolayout Autolayout,就是“自动布局” 那么,自动布局,它就是专门用来做UI界面的 那么,UI界面,我们为了适应不同屏幕,要进行自动布局, 所以要使用Autolayout 这个Autolayou…

Open3D (C++) 计算矩阵的广义逆

目录 一、算法原理1、广义逆2、计算过程二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。爬虫网站自重,把自己当个人,爬些不完整的误导别人有意思吗???? 一、算法原理 1、广义逆 非方阵不存在逆,但是存在广义逆(伪逆)。对于一个矩阵

数据仓库设计理论

数据仓库设计理论 一、数据仓库基本概念 1.1、数据仓库介绍 数据仓库是一个用于集成、存储和分析大量结构化和非结构化数据的中心化数据存储系统。它旨在支持企业的决策制定和业务分析活动。 1.2、基本特征 主题导向:数据仓库围绕特定的主题或业务领域进行建模…

【网络基础实战之路】基于MGRE多点协议的实战详解

系列文章传送门: 【网络基础实战之路】设计网络划分的实战详解 【网络基础实战之路】一文弄懂TCP的三次握手与四次断开 【网络基础实战之路】基于MGRE多点协议的实战详解 【网络基础实战之路】基于OSPF协议建立两个MGRE网络的实验详解 PS:本要求基于…

记录一次Linux环境下遇到“段错误核心已转储”然后利用core文件解决问题的过程

参考Linux 下Coredump分析与配置 在做项目的时候,很容易遇到“段错误(核心已转储)”的问题。如果是语法错误还可以很快排查出来问题,但是碰到coredump就没办法直接找到问题,可以通过设置core文件来查找问题&#xff0…

About Multiple regression

ps:this article is not very strict,just some ml and mathematic basic knowledge.My english is poor too.So If this passage make you confuse and uncomfortable.Please give me a feedback in the comment :-D Prior to this(在此之前), we learned the concept of sin…

数据结构:单链表的实现(C语言)

个人主页 : 水月梦镜花 个人专栏 : 《C语言》 《数据结构》 文章目录 前言一、单链表实现思路和图解1.节点的定义(SListNode)2.申请一个节点(BuySListNode)3.单链表打印(SListPrint)4.单链表尾插(SListPushBack)5.单链表的头插(SListPushFront)6.单链表的…

vue2-v-show和v-if有什么区别,使用场景分别是什么?

1、v-show和v-if的共同点 在vue中,v-if和v-show的作用效果是相同的(不含v-else),都能控制元素在页面是否显示,在用法上也相同。 当表达式为true的时候,都会占据页面的位置 当表达式为false的时候&#xff…

css3 hover border 流动效果

/* Hover 边线流动 */.hoverDrawLine {border: 0 !important;position: relative;border-radius: 5px;--border-color: #60daaa; } .hoverDrawLine::before, .hoverDrawLine::after {box-sizing: border-box;content: ;position: absolute;border: 2px solid transparent;borde…

TCP的三次握手和四次挥手······详解

1、三次握手 三次握手是建立连接的过程 如图大致为三次握手的流程图: 当客户端对服务端发起连接时,会先发一个包连接请求数据,去询问能否建立连接,该数据包称为 “SYN”包 然后,如果对方同意连接,那么…

九耶|阁瑞钛伦特 Java中,锁的实现方式

在Java中,锁的实现方式有以下几种: 1. Synchronized关键字:使用synchronized关键字可以创建一个互斥锁,它可以保证同一时刻只有一个线程可以进入被synchronized关键字修饰的代码块或方法。 2. ReentrantLock类:Reentr…

二叉树进阶版(C)

文章目录 1.树1.1概念1.2相关定义1.3 表示(左孩子右兄弟) 2.二叉树2.1概念2.2特殊的二叉树1. 满二叉树:2. 完全二叉树: 2.3二叉树的性质2.4练习 3.二叉树的存储结构1. 顺序存储2. 链式存储 4.完全二叉树的代码实现4.1堆的介绍1.堆…

【雕爷学编程】Arduino动手做(184)---快餐盒盖,极低成本搭建机器人实验平台3

吃完快餐粥,除了粥的味道不错之外,我对个快餐盒的圆盖子产生了兴趣,能否做个极低成本的简易机器人呢?也许只需要二十元左右 知识点:轮子(wheel) 中国词语。是用不同材料制成的圆形滚动物体。简…

Qt展示动态波形

Qt展示动态波形 需求描述成品展示实现难点Qt多线程 需求描述 接入串口,配置串口顺序进行接收数据;数据分成两个串口分别传入,使用多线程并发接入;时域数据有两个通道(I,Q),分别以实…

zookeeper入门学习

zookeeper入门学习 zookeeper应用场景 分布式协调组件 客户端第一次请求发给服务器2,将flag值修改为false,第二次请求被负载均衡到服务器1,访问到的flag也会是false 一旦有节点发生改变,就会通知所有监听方改变自己的值&#…

【C++】类和对象-多态

1.多态的基本语法 代码 #include <iostream> using namespace std; /******************************************/ class Animal { public://speak函数就是虚函数//函数前面加上virtual关键字&#xff0c;变成虚函数&#xff0c;//那么编译器在编译的时候就不能确定函数…

DevExpress WPF Tree List组件,让数据可视化程度更高!(二)

DevExpress WPF Tree List组件是一个功能齐全、数据感知的TreeView-ListView混合体&#xff0c;可以把数据信息显示为REE、GRID或两者的组合&#xff0c;在数据绑定或非绑定模式下&#xff0c;具有完整的数据编辑支持。 在上文中&#xff08;点击这里回顾DevExpress WPF Tree …

中介者模式——协调多个对象之间的交互

1、简介 1.1、概述 如果在一个系统中对象之间的联系呈现为网状结构&#xff0c;如下图所示&#xff1a; 对象之间存在大量的多对多联系&#xff0c;将导致系统非常复杂&#xff0c;这些对象既会影响别的对象&#xff0c;也会被别的对象所影响&#xff0c;这些对象称为同事对…