【iOS】——Block底层实现和捕获机制

Block的实质

Block的定义是带有自动变量的匿名函数,下面从源码的角度探究下Block究竟是什么

下面是一个Block的简单实现:

int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...void (^blk)(void) = ^{printf("Block\n");};blk();}return 0;
}

通过clang编译器转换为如下的C++代码:

// 定义Block的实现结构体
struct __block_impl {void *isa;      // 指向所属类的指针int Flags;      // 标志性参数,暂时没用到所以默认为0int Reserved;   // 今后版本升级所需的区域大小。void *FuncPtr;  // 函数指针,指向实际执行的函数,也就是Block中花括号里面的代码内容。
};// 定义具体的Block结构体
struct __main_block_impl_0 {struct __block_impl impl; // 上面定义的Block实现结构体的变量struct __main_block_desc_0* Desc; // Block描述符的指针// 构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) {impl.isa = &_NSConcreteStackBlock; // 设置Block的类型标识impl.Flags = flags;                // 设置标志impl.FuncPtr = fp;                 // 设置Block的执行函数指针Desc = desc;                       // 设置Block的描述符}
};// Block的实际执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n"); // Block执行时输出的内容
}// Block描述符结构体
static struct __main_block_desc_0 {size_t reserved;  // 今后版本升级所需区域的大小(一般填0)size_t Block_size;   // Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; // 初始化Block描述符// 主函数
int main(int argc, const char * argv[]) {// 创建Blockvoid (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));// 调用Block((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);return 0;
}

可以看到^{printf("Block\n");转换成了

// Block的实际执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n"); // Block执行时输出的内容
}

block的匿名函数被转换成普通C语言函数,函数命名按照block语法所属的函数名(此处为main函数)和在该函数中出现的顺序(此处为0)。

这里的_ self相当于OC中的指向对象自身的self,也就是_ self指向block值的变量

_ self参数是__main_block_impl_0的参数

__main_block_impl_0

// 定义具体的Block结构体
struct __main_block_impl_0 {struct __block_impl impl; // 上面定义的Block实现结构体的变量struct __main_block_desc_0* Desc; // Block描述符的指针// 构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) {impl.isa = &_NSConcreteStackBlock; // 设置Block的类型标识impl.Flags = flags;                // 设置标志impl.FuncPtr = fp;                 // 设置Block的执行函数指针Desc = desc;                       // 设置Block的描述符}
};

这里主要定义类两个结构体,第一个成员是__block_impl 结构体,第二个是__main_block_desc_0,和实现构造函数

__block_impl

struct __block_impl {void *isa;//指向所属类的指针int Flags;//标志性参数,暂时没用到所以默认为0int Reserved;//今后版本升级所需的区域大小。void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
};

如果block变量为空的话,这里的FuncPtr指针就会指向错误的地址而不是实际执行的函数,此时就会报错。

__main_block_desc_0

static struct __main_block_desc_0 {size_t reserved;  //今后版本升级所需区域的大小(一般填0)size_t Block_size;   //Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

第一个成员变量指的是今后版本升级所需区域的大小(一般填0)。
第二个成员变量是Block的大小。

__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};:

  • 这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是__main_block_desc_0_DATA。

  • 其中reserved为0,Block_size是sizeof(struct __main_block_impl_0)。

__main_block_impl_0()构造函数

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}

在这个结构体的构造函数里,isa指针保持这所属类的结构体的实例的指针。_mainblockimlp0结构体就相当于Objective-C类对象的结构体,这里的_NSConcreteStackBlock相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现

main函数

// 主函数
int main(int argc, const char * argv[]) {// 创建Block// 使用类型转换将Block结构体的地址转换成函数指针类型void (*blk)(void) = // 将Block结构体转换为函数指针类型((void (*)())&__main_block_impl_0(// Block的执行函数指针(void *)__main_block_func_0, // Block描述符的地址&__main_block_desc_0_DATA));// 调用Block// 获取Block结构体中的FuncPtr成员,并进行必要的类型转换后调用((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);return 0;
}

第一部分是将__main_block_impl_0结构体类型的自动变量,也就是栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk。

第二部分是相当于源代码中的blk(),即使用该Block部分。去掉转换部分就是:

(*blk->impl.FuncPtr)(blk);

使用函数指针调用函数。由Block语法转换的__main_block_impl_0函数的指针被赋值成员变量FunPtr中。

Block捕获机制

block变量捕获就是在执行Block语法的时候,Block语法表达式所使用的自动变量的值是被保存进了Block的结构体实例中,也就是Block自身中。

如果Block外面还有很多自动变量,静态变,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

Untitled 1.png

下面是对于不同变量时Block的捕获:

  • 全局变量: 不捕获
  • 局部变量: 捕获值
  • 静态全局变量: 不捕获
  • 静态局部变量: 捕获指针
  • const修饰的局部常量:捕获值
  • const修饰的静态局部常量:捕获指针

当Block捕获的是变量的值时,在block内部不能修改变量,当Block捕获的是变量的指针时,在block内部可以修改变量

对于全局变量Block是进行直接访问的

全局变量:

int max = 3;
int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...void (^blk)(void) = ^{printf("%d\n", max);};max = 60;blk();}return 0;
}

在这里插入图片描述

局部变量:

    int dmy = 256;int val = 10;const char *fmt = "val = %d\n";void (^blk)(void) = ^{printf(fmt, val);};val = 2;fmt = "These values were changed. val = %d\n";blk();

在这里插入图片描述

静态全局变量

static int max = 3;
int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...void (^blk)(void) = ^{printf("%d\n", max);};max = 60;blk();}return 0;
}

在这里插入图片描述

静态局部变量

static int val = 10;const char *fmt = "val = %d\n";void (^blk)(void) = ^{val = 2;printf(fmt, val);};fmt = "These values were changed. val = %d\n";blk();

在这里插入图片描述

Block捕获普通变量

int main(int argc, const char * argv[]) {int dmy = 256;int val = 10;const char  *fmt = "val = %d\n";void (^blk)(void) = ^{printf(fmt, val);};blk();return 0;
}

转化后的代码:

// Block结构体定义
struct __block_impl {void *isa;        // 指向Block类型的标识int Flags;        // Block标志位int Reserved;     // 保留字段void *FuncPtr;    // 指向Block执行函数的指针
};// 定义特定于main函数的Block实现
struct __main_block_impl_0 {struct __block_impl impl; // 包含Block的基本实现struct __main_block_desc_0* Desc; // 指向Block描述符const char *fmt;            // 格式字符串int val;                    // 值// 构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0): fmt(_fmt), val(_val) {  // 初始化成员变量impl.isa = &_NSConcreteStackBlock; // 设置Block类型标识impl.Flags = flags;               // 设置Block标志位impl.FuncPtr = fp;                // 设置Block执行函数的指针Desc = desc;                      // 设置Block描述符}
};// Block执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt = __cself->fmt;  // 获取格式字符串int val = __cself->val;          // 获取值printf(fmt, val);                // 执行打印操作
}// Block描述符
static struct __main_block_desc_0 {size_t reserved;                 // 保留字段size_t Block_size;               // Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };// 主函数
int main(int argc, const char * argv[]) {int dmy = 256;                   // 未使用的变量int val = 10;                    // 用于输出的整数值const char *fmt = "val = %d\n";  // 输出格式字符串// 创建Blockvoid (*blk)(void) = // 类型转换为函数指针((void (*)())&__main_block_impl_0(// Block执行函数指针(void *)__main_block_func_0, // Block描述符地址&__main_block_desc_0_DATA, // 格式字符串fmt, // 用于输出的整数值val));return 0;
}

与上次不同的是在block内部语法表达式中使用的自动变量(fmt,val)被作为成员变量追加到了_mainblockimpl0结构体中(注意:block没有使用的自动变量不会被追加,如dmy变量)。

struct __main_block_impl_0 {struct __block_impl impl; // 包含Block的基本实现struct __main_block_desc_0* Desc; // 指向Block描述符const char *fmt;            // 格式字符串int val;                    // 值// 构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0): fmt(_fmt), val(_val) {  // 初始化成员变量impl.isa = &_NSConcreteStackBlock; // 设置Block类型标识impl.Flags = flags;               // 设置Block标志位impl.FuncPtr = fp;                // 设置Block执行函数的指针Desc = desc;                      // 设置Block描述符}
};

在Block执行函数中,fmt,var都是从__cself里面获取的,更说明了二者是属于block的.

这两个变量是值传递,而不是指针传递,也就是说Block仅仅截获自动变量的值,所以这就解释了即使改变了外部的自动变量的值,也不会影响Block内部的值。

// Block执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt = __cself->fmt;  // 获取格式字符串int val = __cself->val;          // 获取值printf(fmt, val);                // 执行打印操作
}

Block捕获_ _block修饰的变量

编译器会将_ _block修饰的变量包装成一个对象,变成对象后就可以根据指针地址在block内部去修改外部的变量,block通过**__forwarding**指针去修改变量的值

int main(int argc, const char * argv[]) {__block int val = 10;void (^blk)(void) = ^{val = 1;};return 0;
}

转换后的代码:

//__block说明符修饰后的变量的结构体
struct __Block_byref_val_0 {void *__isa;  //指向所属类的指针__Block_byref_val_0 *__forwarding;  //指向自己的内存地址的指针int __flags;  //标志性参数,暂时没用到所以默认为0int __size;  //该结构体所占用的大小int val;  //该结构体存储的值,即原变量的赋的值
};//block本体
struct __main_block_impl_0 {struct __block_impl impl;  //block的主体struct __main block desc 0* Desc;  //存储该block的大小__Block_byref_val_0 *val;  //__block修饰的变量的值//构造函数__main_block_impl_0(void *fp, struct __main_block_desc 0 *desc, __Block_byrefval_0 *_val, int flags=0) : val(_val->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;
};//封装的block逻辑,存储了block的代码块
static void __main_block_func_0(struct__main_block_impl_0 *_cself) {__Block_byref_val_0 *val =__cself->val;(val->__forwarding->val) = 1;
}
static void_main_block_copy_0(struct __main_block_impl_0* dst, struct __main_block_impl_0* src) {//根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用_Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}static void __main_block_dispose_0(struct __main_block_imp1_0* src) {//自动释放引用的auto变量(相当于release)_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}static struct __main_block_desc_0 {unsigned long reserved;  //保留字段unsigned long Block_size;  //block大小void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);  //copy的函数指针,下面使用构造函数进行了初始化void (*dispose)(struct __main_block_impl_0*);  //dispose的函数指针,下面使用构造函数进行了初始化
}//构造函数,初始化保留字段、block大小及两个函数__main_block_desc_0_DATA = {0,sizeof(structmain_block_impl_0),__main_block_copy_O, __main_block_dispose_0
};
int main() {//之前的 __block int val = 10;变成了结构体实例struct __Block_byref_val_0 val = {0,  //isa指针&val,  //指向自身地址的指针0,  //标志变量sizeof(__Block_byref_val_0),  //block大小10  //该数据的值};blk = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);return 0;
}

通过源代码发现__block说明符修饰的变量的结构体__多了个指针_ _Block_byref_val_0 *__forwarding,指向自己的内存地址的指针

struct __Block_byref_val_0 {void *__isa;  //指向所属类的指针__Block_byref_val_0 *__forwarding;  //指向自己的内存地址的指针int __flags;  //标志性参数,暂时没用到所以默认为0int __size;  //该结构体所占用的大小int val;  //该结构体存储的值,即原变量的赋的值
};

__main_block_func_0打印的并不是_ _Block_byref_val_0* val而是(val->__forwarding->val)并且 _ _block说明符修饰的变量变成了一个结构体

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_val_0 *val = __cself->val; (val->__forwarding->val) = 1;printf("val = %d\n", (val->__forwarding->val));
}

那么为什么会有成员变量__forwarding呢?__

因为__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上时都能够正确的访问__block变量。

在这里插入图片描述

总结

  • Block的本质是一个对象,其内部的第一个成员是isa指针,通过内部的FuncPtr指针指向实际执行的函数,也就是Block中花括号里面的代码内容。
  • Block只会捕获自己需要使用的自动变量并将其保存进了Block的结构体实例中,也就是Block自身中。
  • Block捕获普通的局部变量只是通过构造函数将它的值传递进了Block的结构体实例中的成员,因此不能在Block内部修改值,并且在Block外修改的值也不会影响Block捕获时的值
  • Block捕获static修饰的局部变量是捕获它的指针,因此可以在Block内部修改值
  • Block捕获_ _Block变量时会将其转换成结构体并且生成_ _Block_byref_val_0 *__forwarding指针,指向自己的内存地址的指针,通过该指针就能访问和修改 _ _Blcok变量

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

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

相关文章

WordPress原创插件:搜索引擎抓取首图seo图片

WordPress原创插件:搜索引擎抓取首图seo图片 插件设置 插件将在网站头部添加适当的meta标签,以便百度等搜索引擎抓取指定的固定图像。 插件下载 https://download.csdn.net/download/huayula/89596527

Docker容器数据库启动,如何用别名JAR jdbc:postgresql://别名:5432/postgres

如果想了解为啥这样做得同学,请去看这个文章 Docker容器网络(七)_host.docker.internal-CSDN博客 因为docker0网络,需要用别名的话,还得在host文件加 dockerIp(172.0.0.2) 别名 怎么查, docker network …

C语言:扫雷游戏实现

一、扫雷游戏的分析和设计 扫雷游戏想必大家都玩过吧,初级的玩法是在一个9*9的棋盘上找到没有雷的格子,而今天我们就要做的就是9*9扫雷游戏的实现。 1、游戏功能和规则 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现继续玩或者退出游戏扫雷的棋盘…

嵌入式学习第11天——C语言选择结构

2024年7月29日 第11天 选择&#xff08;分支&#xff09;结构 分支结构&#xff1a;又被称为选择结构 概念 选择结构&#xff1a;根据条件成立与否&#xff0c;选择相应的操作。 条件构建 关系表达式&#xff1a;含有关系运算符的表达式&#xff08;>,<,>,<,!…

贪心系列专题篇三

目录 单调递增的数字 坏了的计算器 合并区间 无重叠区间 用最少数量的箭 声明&#xff1a;接下来主要使用贪心法来解决问题&#xff01;&#xff01;&#xff01; 单调递增的数字 题目 思路 如果我们遍历整个数组&#xff0c;然后对每个数k从[k,0]依次遍历寻找“单调递…

【计算机毕设论文】基于SpringBoot的诗词管理系统

&#x1f497;博主介绍&#xff1a;✌全平台粉丝5W,高级大厂开发程序员&#x1f603;&#xff0c;博客之星、掘金/知乎/华为云/阿里云等平台优质作者。 【源码获取】小伙伴可以关注我 感兴趣的可以先收藏起来&#xff0c;同学门有不懂的毕设选题&#xff0c;项目以及论文编写等…

一款DC双向马达驱动电路的桥式驱动芯片 - SS6286L

电机驱动芯片 - SS6286L是一款DC双向马达驱动电路&#xff0c;它适用玩具类别的电机驱动、自动阀门电机驱动、电磁门锁驱动等。它有两个逻辑输入端子用来控制电机前进、后退及制动。该电路具有良好的抗干扰性&#xff0c;微小的待机电流、低的输出内阻&#xff0c;同时&#xf…

Could not install packages due to an EnvironmentError: [WinError 5]

Could not install packages due to an EnvironmentError: [WinError 5] 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城…

通配符https证书的申请途径和配置方法

一、通配符SSL证书的功能 通配符SSL证书&#xff0c;也被称为泛域名证书&#xff0c;是一种特殊类型的SSL证书&#xff0c;它能够保护一个主域名及其所有次级子域名&#xff08;不可跨级保护&#xff09;。例如&#xff0c;如果您的主域名是example.com&#xff0c;那么一个通…

四款2024年不入耳耳机排行榜分享,入耳式耳机戴不舒服的可入手

随着长时间的入耳式耳机佩戴&#xff0c;很多人都发现其实入耳式耳机逐步成为了引起耳朵感染疾病的原因之一。因为经常佩戴入耳式耳机会导致耳道湿度高、微生物更容易生长&#xff0c;进而耳道分泌物也更多了&#xff0c;所以其实现在流行的不入耳耳机&#xff0c;也就是开放式…

昇思25天学习打卡营第1天|快速入门实操教程

昇思25天学习打卡营第1天|快速入门实操教程 目录 昇思25天学习打卡营第1天|快速入门实操教程 一、MindSpore内容简介 主要特点&#xff1a; MindSpore的组成部分&#xff1a; 二、入门实操步骤 1. 安装必要的依赖包 2. 下载并处理数据集 3. 构建网络模型 4. 训练模型…

vue项目中 watch监听的用法

1.导航栏有个搜索功能&#xff0c;点击搜索是路由跳转到课程列表这个页面&#xff0c; 2. 搜索关键字课程&#xff0c;路由跳转到openClass页面&#xff0c;并且携带query参数 search <div class"inputStyle" style"width: 280px;"><el-input v…

Tsucs吸引子:动态系统的神秘引力——从状态方程到稳定解的追寻

Tsucs吸引子&#xff1a;动态系统的神秘引力——从状态方程到稳定解的追寻 Tsucs吸引子的直观类比 你可以把Tsucs吸引子想象成一个神秘的“黑洞”&#xff0c;它不断地“吸引”着动态系统中的各种状态&#xff0c;使它们最终都落入一个稳定的“中心点”&#xff0c;就像黑洞吞噬…

c程序杂谈系列(职责链模式与if_else)

从处理器的角度来说&#xff0c;条件分支会导致指令流水线的中断&#xff0c;所以控制语句需要严格保存状态&#xff0c;因为处理器是很难直接进行逻辑判断的&#xff0c;有可能它会执行一段时间&#xff0c;发现出错后再返回&#xff0c;也有可能通过延时等手段完成控制流的正…

【C++/STL】:哈希 -- 线性探测哈希桶

目录 &#x1f4a1;前言一&#xff0c;unordered系列容器二&#xff0c;哈希2.1 哈希的概念2.2 哈希函数2.3 哈希冲突 三&#xff0c;哈希冲突解决(重点)3.1 开放定址法3.2 哈希桶(重点) 四&#xff0c;线性探测的实现4.1 线性探测的基本框架4.2 插入操作4.3 查找操作4.4 删除操…

Let‘s Encrypt

启动Https,需要从证书授权机构(简称CA)处获取一个证书,Lets Encrypt就是一个CA Lets Encrypt上可以获得免费的ssl证书,时间是3个越 下面通过Nginx和Lets Encrypt让网站升级到HTTPS Certbot简介 Certbot是Lets Encrypt官方推荐的获取证书的客户端 Centos 7.9操作 yum install …

openai版本不适配问题(在windows系统下openai migrate)

问题如下&#xff1a; 方法&#xff1a; 参考官网v1.0.0 Migration Guide openai/openai-python Discussion #742 GitHub 具体步骤&#xff1a; 1、curl -fsSL https://docs.grit.io/install | bash -x 但该命令可能不好用————将‘curl -fsSL https://docs.grit.io/i…

谷歌AI拿下IMO奥数银牌,数学推理模型AlphaProof面世,强化学习 is so back

用上了 Gemini 大模型与 AlphaZero 强化学习算法,几何、代数、数论全都会。 对于 AI 来说,奥数不再是问题了。 本周四,谷歌 DeepMind 的人工智能完成了一项壮举:用 AI 做出了今年国际数学奥林匹克竞赛 IMO 的真题,并且距拿金牌仅一步之遥。 上周刚刚结束的 IMO 竞赛共有…

深入分析 Android ContentProvider (九)

文章目录 深入分析 Android ContentProvider (九)ContentProvider 的高级使用及最佳实践&#xff08;续&#xff09;1. 复杂查询与联合查询复杂查询示例 2. 数据同步与一致性示例&#xff1a;使用事务确保数据一致性 3. 数据分页加载示例&#xff1a;分页加载数据 4. 内容提供者…

【CN】Argo 持续集成和交付(二)

7.25.通知 概述 Argo CD 通知持续监控 Argo CD 应用程序&#xff0c;并提供一种灵活的方式来通知用户应用程序状态的重要变化。使用灵活的触发器和模板机制&#xff0c;可以配置何时发送通知以及通知内容。Argo CD 通知包含有用的触发器和模板目录。因此&#xff0c;可以直接…