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后的结果如下所示:


相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
相关文章
|
3月前
|
消息中间件 人工智能 弹性计算
打造你的专属 AI 导游:基于 RocketMQ 的多智能体异步通信实战
在现代 AI 应用中,多智能体(Multi-Agent)系统已成为解决复杂问题的关键架构。然而,随着智能体数量增多和任务复杂度提升,传统的同步通信模式逐渐暴露出级联阻塞、资源利用率低和可扩展性差等瓶颈。为应对这些挑战,RocketMQ for AI 提供了面向 AI 场景的异步通信解决方案,通过事件驱动架构实现智能体间的高效协作。本文将探讨和演示如何利用 RocketMQ 构建一个高效、可靠且可扩展的多智能体系统,以解决企业级 AI 应用中的核心通信难题。
298 41
|
2月前
|
人工智能 自然语言处理 API
AI应用开发-003-Coze平台
智能体是具备感知、决策与行动能力的AI系统,由大语言模型、记忆、规划、工具使用和行动五大核心构成。通过Coze平台可零代码搭建智能体,结合插件、知识库与工作流,实现个性化AI应用,广泛应用于客服、助理、开发等场景。
382 2
AI应用开发-003-Coze平台
|
2月前
|
缓存 前端开发 NoSQL
WordPress网站打开慢优化指南:全链路提速方案(2026实测有效)
作为全球最流行的建站系统,WordPress凭借易用性和丰富生态占据超40%的市场份额,但不少站长在部署后都会遇到打开慢、加载卡顿的问题。尤其在高峰时段,页面加载动辄5秒以上,不仅让访客流失率飙升,还会直接拉低搜索引擎排名。其实WordPress本身并不慢,多数卡顿源于服务器配置不当、资源未优化等细节问题。本文结合2026最新优化实践,分享一套通用型WordPress提速方案,从底层服务器到前端资源全链路优化,新手也能轻松上手。
|
测试技术 Python
【手机群控】 利用Python与uiautomator2实现
使用Python的uiautomator2库进行多设备自动化测试,涉及环境准备(Python、uiautomator2、adb连接设备)和代码实现。通过`adb devices`获取设备列表,使用多进程并行执行测试脚本,每个脚本通过uiautomator2连接设备并获取屏幕尺寸。注意设备需开启USB调试并授权adb。利用多进程而非多线程,因Python的GIL限制。文章提供了一种提高测试效率的方法,适用于大规模设备测试场景。
1355 2
【手机群控】 利用Python与uiautomator2实现
|
网络协议
DNS正向解析实现
文章介绍了DNS正向解析的实现,包括资源记录的定义、配置区域解析记录的步骤,并通过实际操作展示了如何为"yinzhengjie.com"域名配置DNS解析记录。
344 2
DNS正向解析实现
|
关系型数据库 MySQL Java
MySQL 巨坑:update 更新慎用影响行数做判断!!!
MySQL 巨坑:update 更新慎用影响行数做判断!!!
2351 0
MySQL 巨坑:update 更新慎用影响行数做判断!!!
|
存储 网络协议
最最最常用的10个nslookup命令,网工看了,必藏!
最最最常用的10个nslookup命令,网工看了,必藏!
334 1
|
存储 Linux 数据处理
深入解析Linux下的struct tm结构体:时间信息的存储与操作
在Linux系统编程中,`struct tm` 结构体是一个重要的数据结构,用于存储和表示日期和时间信息。它提供了丰富的成员变量,允许开发者访问和操作年、月、日、时、分、秒等时间单位。本文将详细介绍 `struct tm` 结构体的成员变量、使用方法以及如何将时间信息以更易读的方式呈现给用户。
5069 3
|
机器学习/深度学习 传感器 算法
【智能优化算法】基于自适应反馈调节因子的阿基米德优化算法(IAOA)附matlab代码
【智能优化算法】基于自适应反馈调节因子的阿基米德优化算法(IAOA)附matlab代码

热门文章

最新文章