「OC」NSArray的底层逻辑和遍历方法

「OC」NSArray的底层逻辑和遍历方法

文章目录

  • 「OC」NSArray的底层逻辑和遍历方法
    • 前言
    • NSArray的底层逻辑
      • 占位符
      • init后的空NSArray
      • 只有单个元素的NSArray
      • 大于一个元素的NSArray
      • 可变数组NSMutableArray
      • 总结图片
    • 遍历NSArray
      • 1. for循环
      • 2. 枚举
      • 3.for—in
      • 4. 多线程
        • 1.for 循环&for in
        • 2.block循环
    • 参考文章

前言

最近在看github上大佬写的关于NSArray的博客,正好最近在对NSArray的相关知识进行梳理,于是整理成博客。

NSArray的底层逻辑

我们提供一段代码作为开头

 NSArray *placeholder = [NSArray alloc];NSArray *arr1 = [[NSArray alloc] init];NSArray *arr2 = [[NSArray alloc] initWithObjects:@0, nil];NSArray *arr3 = [[NSArray alloc] initWithObjects:@0, @1, nil];NSArray *arr4 = [[NSArray alloc] initWithObjects:@0, @1, @2, nil];NSLog(@"placeholder: %s", object_getClassName(placeholder));NSLog(@"arr1: %s", object_getClassName(arr1));NSLog(@"arr2: %s", object_getClassName(arr2));NSLog(@"arr3: %s", object_getClassName(arr3));NSLog(@"arr4: %s", object_getClassName(arr4));NSMutableArray *mPlaceholder = [NSMutableArray alloc];NSMutableArray *mArr1 = [[NSMutableArray alloc] init];NSMutableArray *mArr2 = [[NSMutableArray alloc] initWithObjects:@0, nil];NSMutableArray *mArr3 = [[NSMutableArray alloc] initWithObjects:@0, @1, nil];NSLog(@"mPlaceholder: %s", object_getClassName(mPlaceholder));NSLog(@"mArr1: %s", object_getClassName(mArr1));NSLog(@"mArr2: %s", object_getClassName(mArr2));NSLog(@"mArr3: %s", object_getClassName(mArr3));

我们可以得到以下结果:

image-20240929215722751

了解了上面得出的结果,我们就可以开始进行相关的分析了。

占位符

我们先从第一个开始分析,不论是可变数组还是不可变数组,我们对其只进行alloc的结果得到的都是一个名为 __NSPlaceholderArray的类,顾名思义这个类就是用来进行占位的,我们对其进行再进一步的探究

NSArray *placeholder1 = [NSArray alloc];
NSArray *placeholder2 = [NSArray alloc];
NSLog(@"placeholder1: %p", placeholder1);
NSLog(@"placeholder2: %p", placeholder2);

我们可以看到

NSArray *placeholder1 = [NSArray alloc];
NSArray *placeholder2 = [NSMutableArray alloc];
NSLog(@"placeholder1: %p", placeholder1);
NSLog(@"placeholder2: %p", placeholder2);

image-20240929220323749

我们可以看到生成的占位符地址是一样的

这里我直接引用大佬的结论

可以猜测,这里是生成了一个单例,在执行init之后就被新的实例给更换掉了。该类内部只有一个isa指针,除此之外没有别的东西。 由于苹果没有公开此处的源码,我查阅了别的类似的开源以及资料,得到如下的结论:

当元素为空时,返回的是__NSArray0的单例;

为了区别可变和不可变的情况,在init的时候,会根据是NSArray还是NSMutableArray来创建immutablePlaceholdermutablePlaceholder,它们都是__NSPlaceholderArray类型的。

init后的空NSArray

对于空的NSArray的来说,因为NSArray是不可变的,也就是说,就是空的NSArray有且只有一种,那我们想到可以使用单例来进行实现。我们可以打印出来的类名__NSArray0

关于这个__NSArray0是否为单例我们可以以下程序进行验证

#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {NSArray *array = [NSArray array];NSUInteger rc = [array retainCount];NSLog(@"%lu", rc);}return 0;
}

我们通过打印出来的数字,是一个很大的数字,就足以说呢我们创建的是一个单例了

只有单个元素的NSArray

接下来我们看到只有一个元素的NSArray,由于NSArray是不可变数组,我们可以看到打印出来的类名叫做__NSSingleObjectArrayI

结构定义如下

@interface __NSSingleObjectArrayI : NSArray
{id object;
}
@end

大于一个元素的NSArray

我们可以看到大于一个元素的NSArray,程序打印的是__NSArrayI

__NSArrayI的结构如下

@interface __NSArrayI : NSArray
{NSUInteger _used;id _list[0];
}
@end

_used是数组的元素个数,调用[array count]时,返回的就是_used的值。 这里我们可以把id _list[0]当作id *_list来用,这个id _list[0]其实被叫做柔性数组

当你定义一个结构体并在结构体的末尾放置一个大小为 0 的数组时,编译器会将这个数组成员视为柔性数组。这个柔性数组的大小是在运行时根据需要动态分配内存来确定的。因此,通过访问结构体的柔性数组成员,你实际上可以访问在结构体后面额外分配的内存空间,这样就能实现动态长度的数组。

可变数组NSMutableArray

在程序之中我们可以知道,无论是什么情况,我们创建的NSMutableArray类名都是__NSArrayM,由于可变数组是可以随意添加和删除元素的,其对应的结构如下:

@interface __NSArrayM : NSMutableArray
{NSUInteger _used;NSUInteger _offset;int _size:28;int _unused:4;uint32_t _mutations;id *_list;
}
@end

__NSArrayM稍微复杂一些,但是同样的,它的内部对象数组也是一块连续内存id* _list,正如__NSArrayIid _list[0]一样 _used:当前对象数目 _offset:实际对象数组的起始偏移,这个字段的用处稍后会讨论 _size:已分配的_list大小(能存储的对象个数,不是字节数) _mutations:修改标记,每次对__NSArrayM的修改操作都会使_mutations加1 id *_list是个循环数组.并且在增删操作时会动态地重新分配以符合当前的存储需求。

注:关于int _size:28;之中的28是位域,表示的是在内存之中占28个比特位,28 + 4 = 32 正好是一个int类型的字节大小

简单介绍之后,我们再来着重的讲一下刚刚介绍的offsetunused

__NSArrayM内存存储方式是使用环形缓存区的策略进行存储的,环形缓存区其实就像是一个环形队列,在我们进行增删操作的时候,程序会对这个_size进行动态的修正。

现在我用一个例子来解释__NSArrayM的工作原理,我们现在有一个包含5个对象,总大小_size为6的_list为,初始化完成之后 _offset = 0,_used = 5,_size=6

image

在末端追加3个对象后: _offset = 0,_used = 8,_size=8 _list已重新分配

image

删除对象A: _offset = 1,_used = 7,_size=8

image

删除对象E: _offset = 2,_used = 6,_size=8 B,C往后移动了,E的空缺被填补

image

在末端追加两个对象: _offset = 2,_used = 8,_size=8 _list足够存储新加入的两个对象,因此没有重新分配,而是将两个新对象存储到了_list起始端

image

对于我们环形缓存区来说,如果我们需要从两端任意一端进行增删操作,就不需要我们进行对数组内存进行的移动,如果是需要我们对中间进行操作,那么其实程序只会对操作当前位置元素最少的一端。

总结图片

img

遍历NSArray

前面的内容介绍完了NSArray的相关的底层逻辑了,现在开始我们对于NSArray的遍历进行探究了,下面介绍一下遍历的相关方式

1. for循环

for (int i = 0;  i < array.count; ++i) {id object = array[i];}

2. 枚举

关于枚举,我们就是使用NSEnumerator进行相关操作

NSEnumerator *enumerator = [array objectEnumerator];
id object;
while((object = [enumerator nextObject])!= nil){}

3.for—in

for (id object in anArray) {}

4. 多线程

通过block回调,在子线程中遍历,对象的回调次序是乱序的,而且调用线程会等待该遍历过程完成:

[array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {xxx}];

其实关于遍历其实绕来绕去离不开性能二字,下图就是各种遍历方式的性能

[img

横轴为遍历的对象数目,纵轴为耗时,单位us. 从图中看出,在对象数目很小的时候,各种方式的性能差别微乎其微。随着对象数目的增大, 性能差异才体现出来. 其中for in的耗时一直都是最低的,当对象数高达100万的时候,for in耗时也没有超过5ms. 其次是for循环耗时较低. 反而,直觉上应该非常快速的多线程遍历方式却是性能最差的。

其中关于他们之间的性能差异产生的原因,我找到了这篇文章 Objective-C 数组遍历的性能及原理,有兴趣的读者可以自行阅读,由于笔者能力还有所欠缺,还无法完整的内容学完,这里就直接引用作者得到的结论:

1.for 循环&for in

以for—in为例。forin遵从了NSFastEnumeration协议,它只有一个方法:

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)stateobjects:(id *)stackbuffer count:(NSUInteger)len;

它直接从C数组中取对象。对于可变数组来说,它最多只需要两次就可以获取全部全速。如果数组还没有构成循环,那么第一次就获得了全部元素,跟不可变数组一样。但是如果数组构成了循环,那么就需要两次,第一次获取对象数组的起始偏移到循环数组末端的元素,第二次获取存放在循环数组起始处的剩余元素。 而for循环之所以慢一点,是因为for循环的时候每次都要调用objectAtIndex: 假如我们遍历的时候不需要获取当前遍历操作所针对的下标,我们就可以选择forin。

2.block循环

这种循环虽然是最慢的,但是我们在遍历的时候可以直接从block中获取更多的信息,并且可以修改块的方法签名,以免进行类型转换操作。

for(NSString *key in aDictionary){NSString *object = (NSString *)aDictionary[key];
}
NSDictionary *aDictionary = /*...*/;
[aDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key,NSString *obj,BOOL *stop){}];

NSEnumerationConcurrent+Block的方式耗时最大,我认为是因为它采用了多线程,就这个方法来讲,多线程的优势并不在于遍历有多快,而是在于它的回调在各个子线程,如果有遍历+分别耗时计算的场景,这个方法应该是最适合的,只是此处只测遍历速度,它光启动分发管理线程就耗时不少,所以性能落后了.

并且如果需要需要并发的时候,也可以方便的使用dispatch_group/dispatch_apply。

另外还有一点:如果数组的数量过多,除了block遍历,其他的遍历方法都需要添加autoreleasePool方法来优化。block遍历就不需要,因为系统在实现它的时候就已经实现了相关处理。

参考文章

NSArray原理及遍历方法探究

Exposing NSMutableArray

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

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

相关文章

MES管理系统对中小企业有哪些帮助

MES管理系统解决方案对中小企业具有显著的帮助&#xff0c;主要体现在以下几个方面&#xff1a; 一、提升生产效率 MES管理系统能够实时监控生产过程&#xff0c;提供准确的生产数据和及时的反馈。这种实时监控与数据分析能力&#xff0c;使中小企业能够精准把握生产脉搏&…

华为---MUX VLAN简介及示例配置

目录 1. 产生背景 2. 应用场景 3. 主要功能 4. 基本概念 5. 配置步骤及相关命令 6.示例配置 6.1 示例场景 6.2 网络拓扑图 6.3 配置代码 6.4 配置及解析 6.5 测试验证 配置注意事项 1. 产生背景 MUX VLAN&#xff08;Multiplex VLAN&#xff09;提供了一种通过VLA…

【JavaScript】JavaScript 与 V8

浏览器中运行 html 和 css 代码&#xff1a; html 和 css 执行过程&#xff1a; js 由 js 引擎&#xff08;比如现在最为主流的 V8&#xff09;执行。 高级的编程语言都是需要转成最终的机器指令来执行的&#xff1b;事实上我们编写的JavaScript无论你交给浏览器或者Node执行&…

升级 OpenSSL 的详细步骤(解决 SSH 漏洞的前提)

目录 前言1. 准备工作1.1 安装必要的依赖1.2 下载 OpenSSL 源码 2. 解压和配置2.1 解压文件2.2 配置编译参数 3. 编译和安装3.1 编译源码3.2 安装 OpenSSL 4. 验证安装5. 解决 SSH 漏洞的必要性6. 结语 前言 在信息安全的时代&#xff0c;服务器的安全性至关重要。特别是在互联…

高被引算法GOA优化VMD,结合Transformer-SVM的轴承诊断,保姆级教程!

本期采用2023年瞪羚优化算法优化VMD&#xff0c;并结合Transformer-SVM实现轴承诊断&#xff0c;算是一个小创新方法了。需要水论文的童鞋尽快&#xff01; 瞪羚优化算法之前推荐过&#xff0c;该成果于2023年发表在计算机领域三区SCI期刊“Neural Computing and Applications”…

智能配音软件哪款好?分享5个搞怪软件

想要让视频或社交媒体内容更加生动有趣&#xff1f;搞笑配音软件是个不错的选择。 无论是嘻哈风格的视频&#xff0c;还是搞怪的段子&#xff0c;合适的配音都能让内容增色不少。 今天&#xff0c;就让我们来探索六个文字配音软件&#xff0c;它们不仅能帮你实现搞笑配音&…

Centos7安装RocketMQ[图文教程]

文章目录 RocketMQ介绍基于Linux服务部署RocketMQ&#xff08;单机&#xff09;配置JDK环境下载RocketMQ部署RocketMQ1、解压2、修改VM参数3、配置环境变量4、编写Service文件5、启动服务 基于Docker方式部署RocketMQ安装Docker编写docker-compose文件启动RocketMQ服务 部署Roc…

嵌入式面试——FreeRTOS篇(四) 信号量

本篇为&#xff1a;FreeRTOS信号量篇 信号量 1、什么是信号量 答&#xff1a; 信号量是一种解决同步问题的机制&#xff0c;可以实现对共享资源的有序访问。 2、信号量简介 答&#xff1a; 当计数值大于0&#xff0c;表示有信号量资源。当释放信号量&#xff0c;信号量计数…

SQL 干货 | 使用 EXISTS 编写 SELECT 查询

基于 SQL 中的 EXISTS 运算符为我们提供了一种基于其他数据是否存在&#xff08;或不存在&#xff09;来检索数据的简便方法。更具体地说&#xff0c;它是一个逻辑运算符&#xff0c;用于评估子查询的结果&#xff0c;并返回一个布尔值&#xff0c;该值指示是否返回了行。尽管 …

Elasticsearch 实战应用

Elasticsearch 实战应用 引言 Elasticsearch 是一个分布式、RESTful 风格的搜索和分析引擎&#xff0c;能够快速、实时地处理大规模数据&#xff0c;广泛应用于全文搜索、日志分析、推荐系统等领域。在这篇博客中&#xff0c;我们将从 Elasticsearch 的基本概念入手&#xff…

ubuntu 开放 8080 端口快捷命令

文章目录 查看防火墙状态开放 80 端口开放 8080 端口开放 22端口开启防火墙重启防火墙**使用 xhell登录**&#xff1a; 查看防火墙状态 sudo ufw status [sudo] password for crf: Status: inactivesudo ufw enable Firewall is active and enabled on system startup sudo…

curl执行报【先没有那个文件或目录】解决办法

开发微信发过了curl命令后&#xff0c;执行报错 是空格导致的&#xff0c;解决办法是打开下面网址重新输入空格即可 在线curl命令转代码 删除这个空格 重新输入空格

SCI论文快速排版:word模板一键复制样式和格式【重制版】

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 SCI论文快速排版&#xff1a;word模板一键复制样式和格式&#xff1a;视频操作 SCI论文快速排版&#xff1a;word模板一键复制样式和格式【重制版】 模板与普通文档的区别 为了让读者更好地了解模板&#xff…

国产工具链GCKontrol-GCAir助力控制律开发快速验证

前言 随着航空领域技术的不断发展&#xff0c;飞机的飞行品质评估和优化成为了航空领域的一个重要任务&#xff0c;为了确保飞行器在各种复杂条件下的稳定性&#xff0c;控制律设计过程中的模型和数据验证需要大量仿真和测试。 本文将探讨基于世冠科技的国产软件工具链GCKont…

前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)

前端Vue字体优化三部曲&#xff08;webFont、font-spider、spa-font-spider-webpack-plugin&#xff09; 引言 最近前端引入了UI给的思源黑体字体文件&#xff0c;但是字体文件过于庞大&#xff0c;会降低页面首次加载的速度&#xff0c;目前我的项目中需要用到如下三个字体文…

IP协议报文

一.IP协议报头结构 二.IP协议报头拆解 1.4位版本 实际上只有两个取值&#xff0c;分别是4和6&#xff0c;4代表的是IPv4&#xff0c;6代表的是IPv6。 2.4位首部长度 IP协议报头的长度也是边长的&#xff0c;单位是*4&#xff0c;这里表示的大小为0~15&#xff0c;当数值为1…

从FastBEV来学习如何做PTQ以及量化

0. 简介 对于深度学习而言&#xff0c;通过模型加速来嵌入进C是非常有意义的&#xff0c;因为本身训练出来的pt文件其实效率比较低下&#xff0c;在讲完BEVDET后&#xff0c;这里我们将以CUDA-FastBEV作为例子&#xff0c;来向读者展示如何去跑CUDA版本的Fast-BEV&#xff0c;…

刷题 链表

面试经典150题 - 链表 141. 环形链表 class Solution { public:bool hasCycle(ListNode *head) {ListNode* slow head, *fast head;while (fast ! nullptr && fast->next ! nullptr) {slow slow->next;fast fast->next->next;if (slow fast) {return…

java9的juc包中的Flow接口(响应式编程/发布订阅模式)

前言 在java9的juc包中有一个Flow接口&#xff0c;里面有几个接口 分别为 Publisher 发布者Subscriber 订阅者 Subscription 订阅关系 Processor 中间操作用来完成发布订阅模式的响应式开发 我的环境为java17 响应式编程 底层&#xff1a;基于数据缓冲队列消息驱动模型异…

简单的网络爬虫爬取视频

示例代码爬取一个周杰伦相关视频 import requests# 自己想下载的视频链接 video_url https://vdept3.bdstatic.com/mda-qg8cnf4bw5x6bjs5/cae_h264/1720516251158906693/mda-qg8cnf4bw5x6bjs5.mp4?v_from_shkapp-haokan-hbf&auth_key1728497433-0-0-4a32e13f751e04754e4…