block有哪些对象类型,你知道了吗?

简介: 从block的本质这篇文章中,我们已经知道block的本质是一个OC对象。既然是OC对象。那肯定有对象类型,还是从block的本质这篇文章中搜索impl.ipa字段,发现block指向了一个对象 _NSConcreteStackBlock。那么block还有什么类型呢?怎么实现呢?它们又是分配在哪些内存区呢?让我们带着这些疑问往下看。


1、探寻block类型

既然block是对象,可以通过调用class方法或者isa指针查看具体类型,通过superclass获取其父类,最终都是继承自NSBlock类型


#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"Hello");
        };
        NSLog(@"----%@",[block class]);
        NSLog(@"----%@",[[block class] superclass]);
        NSLog(@"----%@",[[[block class] superclass] superclass]);
        NSLog(@"----%@",[[[[block class] superclass] superclass] superclass]);
        block();
            }
    return 0;
}


2022-05-07 23:46:46.041962+0800 block-01[68178:2905102] ----__NSGlobalBlock__
2022-05-07 23:46:46.042535+0800 block-01[68178:2905102] ----NSBlock
2022-05-07 23:46:46.042595+0800 block-01[68178:2905102] ----NSObject
2022-05-07 23:46:46.042635+0800 block-01[68178:2905102] ----(null)

从上面的打印日志可以知道:


block对象类型:NSGlobalBlock_

父类:NSBlock

父类的父类:NSObject


2、获取block具体类型


#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block1)(void) = ^{
            NSLog(@"Hello1");
        };
        int age = 10;
        void(^block2)(void) = ^{
            NSLog(@"Hello2:%d",age);
        };
        NSLog(@"----%@",[block1 class]);
        NSLog(@"----%@",[block2 class]);
        NSLog(@"----%@",[[^{
            NSLog(@"Hello3:%d",age);
        } class] class]);
    }
    return 0;
}


2022-05-07 23:59:21.573913+0800 block-01[68605:2913488] ----__NSGlobalBlock__
2022-05-07 23:59:21.574601+0800 block-01[68605:2913488] ----__NSMallocBlock__
2022-05-07 23:59:21.574695+0800 block-01[68605:2913488] ----__NSStackBlock__

由以上可以看到 block 有3种类型:


NSGlobalBlock ( _NSConcreteGlobalBlock )

NSStackBlock ( _NSConcreteStackBlock )

NSMallocBlock ( _NSConcreteMallocBlock )


但是编译完毕之后,情况又不一样了:


struct __main_block_impl_0 {
  struct __block_impl impl;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
  }
};
struct __main_block_impl_1 {
  struct __block_impl impl;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
  }
};
struct __main_block_impl_2 {
  __main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
  }
};


1、发现编译完毕之后,block的类型都是 _NSConcreteStackBlock。

2、这里指向的对象类型名称(编译出来)跟打印出来的类型名称(动态运行后)也是有所不一样的。但是他们是一一对应的。


那么为什么打印出来的跟编译出来的类型不一样?


首先要明白我们的程序最终是以动态运行之后才是准确的。

那么从编译完之后,动态运行期间,还可能根据参数动态调整block对象类型。


3、不同对象类型block的内存分配

2c159fa8a6e24b07abf9649e9e1db531.png

说明:


1、程序区域(.text区):俗称代码段。特点:一般来说内存地址是比较小的,也就是比较前面,是内存的低地址。

2、数据区域(.data区):数据段。特点:一般放一些全局变量。

3、堆:动态分配内存的,如alloc出来的对象。特点:需要开发者申请内存,也需要程序员自己管理内存,当然ARC环境内部已经自动帮我们管理了。

4、栈:放一写局部变量。特点:系统自动分配内存,也会自动销毁内存,当局部变量离开作用域就会自动销毁。


4、如何实现不同对象类型block?

还是先公布结果:

13e68f5cbe1c4072a2b4ab3d91196c3a.png

对于之前在栈上的block,copy到堆上时。当不再使用的时候需要调用 release 释放内存。当然如果我们的编译环境时 ARC 环境,是不需要调用该方法释放,系统会自动管理。


栈空间的 block 不会保持外部变量的引用


代码实现:


注意:这个要在MRC环境下编译。因为ARC环境下编译,它会已经帮我们做了好多事情。

Build Setting 下面,搜索 Automatic Reference Counting 改成NO就可以了。


4.1、NSGlobalBlock 数据区的block

实现条件:block内部没有访问auto就可以。


代码示例:


#import <Foundation/Foundation.h>
void(^block)(void);
void test() {
    block = ^{
        NSLog(@"Hello1");
    };
    NSLog(@"block:%@",[block class]);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}


打印日志:


2022-05-08 01:43:02.443356+0800 block-01[72187:2987373] block:__NSGlobalBlock__
2022-05-08 01:43:02.443825+0800 block-01[72187:2987373] Hello1

4.2、NSStackBlock 栈区的block

实现条件:block内部访问了auto变量


#import <Foundation/Foundation.h>
void(^block)(void);
void test() {
    int age = 10;
    block = ^{
        NSLog(@"Hello1:%d",age);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
     }
    return 0;
}


打印日志:


2022-05-08 01:15:42.458205+0800 block-01[71217:2967549] Hello1:-1074793848

发现运行后的结果,取到的age值是一个脏数据,并不是我们想要的。

原因分析:


1、test函数内部的变量是存放在栈区,待函数运行完毕,内部的对象会全部被销毁。

2、所以在执行block内部函数时,age变量已经销毁,取出来的值一个乱值。


那么怎么处理这个问题呢?


block 方法放到堆上, 即将 __NSStackBlock__ 对象类型升级为 __NSMallocBlock__ 对象类型,实现方式也很简单,直接在后block后面调用一次copy,那么这时返回的block就是在堆上的block。 这样就不会被自动销毁,想要什么时候销毁,由我们程序员自己决定。


具体验证看下一部分。


4.3、NSMallocBlock 堆区的block

实现条件:block内部访问了auto变量,并且执行了一次copy操作

代码实现:


#import <Foundation/Foundation.h>
void(^block)(void);
void test() {
    int age = 10;
    block = [^{
        NSLog(@"Hello1:%d",age);
    } copy];
    NSLog(@"block:%@",[block class]);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        // 此时调用的block时堆上的block
        block();
     }
    return 0;
}

打印日志:


2022-05-08 01:34:07.307367+0800 block-01[71852:2980649] block:__NSMallocBlock__
2022-05-08 01:34:07.308459+0800 block-01[71852:2980649] Hello1:10

结果分析:

从以上知道,block被放到了堆上,age也能正常取值。


4.4、不同对象类型的block 与copy之间的关系

如果数据区的block执行了copy操作呢,会是什么结果?我们可以尝试一下:

代码示例:


#import <Foundation/Foundation.h>
void(^block)(void);
void test() {
    block = [^{
        NSLog(@"Hello1");
    } copy];
    NSLog(@"block:%@",[block class]);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}


2022-05-08 01:50:10.096000+0800 block-01[72430:2991254] block:__NSGlobalBlock__
2022-05-08 01:50:10.103313+0800 block-01[72430:2991254] Hello1

ca061b1b9d174d26be90213999982d9b.png发现block还是 __NSGlobalBlock__ 类型,内存分配在数据区。


总结以上,不同类型的block调用copy后的结果如下所示:


相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
人工智能 关系型数据库 分布式数据库
沉浸式学习PostgreSQL|PolarDB 16: 植入通义千问大模型+文本向量化模型, 让数据库具备AI能力
本文将带领大家来体验一下如何将“千问大模型+文本向量化模型”植入到PG|PolarDB中, 让数据库具备AI能力.
26817 21
沉浸式学习PostgreSQL|PolarDB 16: 植入通义千问大模型+文本向量化模型, 让数据库具备AI能力
|
机器学习/深度学习 JSON 自然语言处理
bert中文文本摘要代码(2)
bert中文文本摘要代码(2)
933 0
|
10月前
|
消息中间件 人工智能 弹性计算
打造你的专属 AI 导游:基于 RocketMQ 的多智能体异步通信实战
在现代 AI 应用中,多智能体(Multi-Agent)系统已成为解决复杂问题的关键架构。然而,随着智能体数量增多和任务复杂度提升,传统的同步通信模式逐渐暴露出级联阻塞、资源利用率低和可扩展性差等瓶颈。为应对这些挑战,RocketMQ for AI 提供了面向 AI 场景的异步通信解决方案,通过事件驱动架构实现智能体间的高效协作。本文将探讨和演示如何利用 RocketMQ 构建一个高效、可靠且可扩展的多智能体系统,以解决企业级 AI 应用中的核心通信难题。
665 78
|
弹性计算 API Python
如何利用通义千问查询阿里云资源
本篇文章详细阐述了如何利用LangChain框架构建一款Python工具,该工具能够调用通义千问大模型来查询和获取阿里云资源信息。
|
10月前
|
数据采集 监控 NoSQL
用n8n打造自愈型用例库与质量知识图谱
三年前,测试团队困于臃肿用例库与信息孤岛。我们基于n8n构建自愈型质量管理系统,打通需求、缺陷与测试数据,实现用例自动修复、智能推荐与持续优化,让质量知识自主进化。
|
存储 人工智能 关系型数据库
拥抱Data+AI|解码Data+AI助力游戏日志智能分析
「拥抱Data+AI」系列第2篇:阿里云DMS+AnalyticDB助力游戏日志数据分析与预测
拥抱Data+AI|解码Data+AI助力游戏日志智能分析
|
JavaScript 算法 安全
基于 WebWorker 的 WebAssembly 图像处理吞吐量深度优化指南
本文深入探讨了基于 WebAssembly (WASM) 和 WebWorker 的高性能图像处理技术,通过优化线程架构与内存管理,实现 4K 图像处理性能比纯 JS 提升 23 倍,同时保持界面流畅(60fps)。文章从技术演进、流水线设计到内存管理实战技巧全面解析,并提供性能瓶颈分析与调优方法。实验表明,在 4K+ 分辨率下,“计算靠近数据”策略可进一步提升性能 40%。最终,方案在生产环境中达成 8K 实时处理 (&lt;30ms/帧),展现浏览器端图像处理的强大潜力。
871 12
|
数据采集 Web App开发 JavaScript
无头浏览器技术:Python爬虫如何精准模拟搜索点击
无头浏览器技术:Python爬虫如何精准模拟搜索点击
|
人工智能 Linux 文件存储
旧台式电脑的 10 种用途
旧台式电脑的 10 种用途
1516 14
|
SQL 缓存 关系型数据库
SQL为什么不建议执行多表关联查询
本文探讨了SQL中不建议执行多表关联查询的原因,特别是MySQL与PG在多表关联上的区别。MySQL仅支持嵌套循环连接,而不支持排序-合并连接和散列连接,因此在多表(超过3张)关联查询时效率较低。文章还分析了多表关联查询与多次单表查询的效率对比,指出将关联操作放在Service层处理的优势,包括减少数据库计算资源消耗、提高缓存效率、降低锁竞争以及更易于分布式扩展等。最后,通过实例展示了如何分解关联查询以优化性能。
657 0