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 statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_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 refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->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 refcount
    result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
    result->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的深入研究]]

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端vue uni-app自定义精美海报生成组件

在当前技术飞速发展的时代,软件开发的复杂度也在不断提高。传统的开发方式往往将一个系统做成整块应用,一个小的改动或者一个小功能的增加都可能引起整体逻辑的修改,从而造成牵一发而动全身的情况。为了解决这个问题,组件化开发逐…

数据结构入门指南:带头双向循环链表

目录 文章目录 前言 1.结构与优势 2.链表实现 2.1 定义链表 2.2 创建头节点 2.3 尾插 2.4 输出链表 2.5 尾删 2.6 头插 2.7头删 2.8 节点个数 2.9 查找 2.10 位置插入 2.11 位置删除 2.12 销毁链表 3. 源码 总结 前言 链表一共有8种结构,但最常用的就是无头单…

NsPack3.x脱壳手记

发现是NsPack3.x的壳 使用ESP守恒快速脱壳 F9遇到popfd后下面的jmp就是通往OEP了 打开LordPE准备转储映像, 首先调整下ImageSize, 接着dump full 接着不要退出目前的调试, 打开Scylla修复IAT, 把OEP的VA地址输入到OEP处, 接着按照如下图所示步骤 完成后如下, 但NsPack3.x…

探索创意之路:稳定扩散AI绘画指南

文章目录 引言第一部分:了解稳定扩散AI绘画1.1 稳定扩散AI绘画简介1.2 稳定扩散AI绘画的优势 第二部分:使用稳定扩散AI绘画2.1 获取稳定扩散AI绘画工具2.2 准备绘画素材和设置参数2.3 进行AI绘画 第三部分:发挥创意,创作精彩绘画3…

结构体和 Json 相互转换(序列化反序列化)

关于 JSON 数据 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也 易于机器解析和生成。RESTfull Api 接口中返回的数据都是 json 数据。 Json 的基本格式如下: { "a": "Hello", "b": "…

【Linux】五种IO模型

文章目录 1. IO基本概念2. 五种IO模型2.1 五个钓鱼的例子2.2 五种IO模型2.2.1 阻塞IO2.2.2 非阻塞IO2.2.3 信号驱动IO2.2.4 IO多路转接2.2.5 异步IO 1. IO基本概念 认识IO IO就是输入和输出,在冯诺依曼体系结构中,将数据从输入设备拷贝到内存就叫输入&am…

STM32使用HAL库中外设初始化MSP回调机制及中断回调机制详解

STM32使用HAL库之Msp回调函数 1.问题提出 在STM32的HAL库使用中,会发现库函数大都被设计成了一对: HAL_PPP/PPPP_Init HAL_PPP/PPPP_MspInit 而且HAL_PPP/PPPP_MspInit函数的defination前面还会有__weak关键字 上面的PPP/PPPP代表常见外设的名称为…

什么是OCR?OCR技术详解

光学字符识别(Optical Character Recognition)简称为“OCR”。ORC是指对包含文本资料的图像文件进行分析识别处理,获取文字及版面信息的技术。 一般包括以下几个过程: 1.图像输入 针对不同格式的图像,有着不同的存储格式和压缩方式。目前&…

JMeter 的使用

文章目录 1. JMeter下载2. JMeter的使用2.1 JMeter中文设置2.2 JMeter的使用2.2.1 创建线程组2.2.2 HTTP请求2.2.3 监听器 1. JMeter下载 官网地址 https://jmeter.apache.org/download_jmeter.cgi https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.2.zip 下载解…

[JAVAee]锁策略

目录 乐观锁与悲观锁 乐观锁 乐观锁的冲突检测 悲观锁 读锁与写锁 重量级锁与轻量级锁 重量级锁 轻量级锁 自旋锁 公平锁与非公平锁 可重入锁与不可重入锁 乐观锁与悲观锁 乐观锁 在乐观锁中,假设数据并不会发生冲突,在正式提交数据时会对数据进行冲突检测,如果发…

ARM 常见汇编指令学习 9 - 缓存管理指令 DC 与 IC

文章目录 ARM64 DC 与 IC 指令 上篇文章:ARM 常见汇编指令学习 8 - dsb sy 指令及 dsb 参数介绍 ARM64 DC 与 IC 指令 AArch64指令集中有两条关于缓存维护(cache maintenance)的指令,分别是IC和DC。 IC 是用于指令缓存操作&…

项目经理必读:领导风格对项目成功的关键影响

引言 项目经理作为一个领导者的角色,他们需要协调各方资源,管理团队,推动项目的进行。为了完成这些任务,项目经理必须具备各种领导风格的灵活性,以应对项目中的各种变数和挑战。在这篇文章中,我们将讨论领…

SpringBoot 项目创建与运行

一、Spring Boot 1、什么是Spring Boot?为什么要学 Spring Boot Spring 的诞生是为了简化 Java 程序的开发的,而 Spring Boot 的诞生是为了简化 Spring 程序开发的。 Spring Boot 翻译一下就是 Spring 脚手架 盖房子的这个架子就是脚手架,…

LeetCode 25题:K个一组翻转链表

题目: 给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。 k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。 你不能只是单纯…

web爬虫第五弹 - JS逆向入门(猿人学第一题)

0- 前言 爬虫是一门需要实战的学问。 而对于初学者来说,要想学好反爬,js逆向则是敲门砖。今天给大家带来一个js逆向入门实例,接下来我们一步一步来感受下入门的逆向是什么样的。该案例选自猿人学练习题。猿人学第一题 1- 拿到需求 进入页面…

在登录界面中设置登录框、多选项和按钮(HTML和CSS)

登录框(Input框)的样式: /* 设置输入框的宽度和高度 */ input[type"text"], input[type"password"] {width: 200px;height: 30px; }/* 设置输入框的边框样式、颜色和圆角 */ input[type"text"], input[type&q…

flutter:占位视图(骨架屏、shimmer)

前言 有时候打开美团,在刚加载数据时会显示一个占位视图,如下: 那么这个是如何实现的呢?我们可以使用shimmer来开发该功能 实现 官方文档 https://pub-web.flutter-io.cn/packages/shimmer 安装 flutter pub add shimmer示例…

Pytest+Allure+Excel接口自动化测试框架实战

1. Allure 简介 简介 Allure 框架是一个灵活的、轻量级的、支持多语言的测试报告工具,它不仅以 Web 的方式展示了简介的测试结果,而且允许参与开发过程的每个人可以从日常执行的测试中,最大限度地提取有用信息。 Allure 是由 Java 语言开发…

【laravel+vue2 】医院信息化手术麻醉临床信息管理系统源码

近年来,医院信息化成为医院领域的推广重点,HIS、LIS、PACS、EMR等信息系统的相继出现,显著提高了医院业务的运行效率。手术麻醉系统作为医院信息系统的一部分,由监护设备数据采集系统和麻醉信息管理系统两个子系统组成。 一、医院…

复亚智能打造全新云平台:让无人机任务管理更智能、更简单

复亚智能全新升级的MindView云平台,对航线规划、任务管理、自动飞行、数据管理等各个环节开展可视化、数字化、智能化监管,从任务到结果的“看得清”、“管得住”、“查得准”,带来更轻松的操作,改善作业效率、安全保障和用户体验…