跃迁引擎

空気を読んだ雨降らないでよ

iOS Research & Development


NSMutableArray原理揭露阅读笔记

NSArray是线性连续内存,这个很好理解。但是NSMutableArray是可以插入和删除的,那么如何做到高效?就比如插入,如何做到尽可能少的移动或者不移动插入元素后其他元素的内存?实现数据结构原理是什么?

NSMutableArray 和 NSArray 的基本区别

  • NSMutableArray 是线程不安全的,内容可随时修改,拷贝自由
  • NSArray 是线程安全的,内容无法随时修改,拷贝自由

数组的基本定义

可以归结为一段能被方便读写的连续内存空间。数组和指针并不相同,不能说:一块被 malloc 过的内存空间等同于一个数组 (一种被滥用了的说法)。

注:malloc 分配

使用一段线性内存空间的一个最明显的缺点是,在下标 0 处插入一个元素时,需要移动其它所有的元素,即 memmove 的原理:

1.jpg

同样地,假如想要保持相同的内存指针作为首个元素的地址,移除第一个元素需要进行相同的动作:

2.jpg

当数组非常大时,这样很快会成为问题。显而易见,直接指针存取在数组的世界里必定不是最高级的抽象。C 风格的数组通常很有用,但 Obj-C 程序员每天的主要工作使得它们需要 NSMutableArray 这样一个可变的、可索引的容器。

NSMutableArray

NSMutableArray 是一个类簇——其具体实现实际上是 NSMutableArray 本身的子类。

类簇是Foundation框架中广泛使用的设计模式。类簇将一些私有的、具体的子类组合在一个公共的、抽象的超类下面,以这种方法来组织类可以简化一个面向对象框架的公开架构,而又不减少功能的丰富性。

类簇 是一群隐藏在通用接口下的与实现相关的类,使得我们编写的代码可以独立于底层实现(因为接口是稳定的)。

如创建NSString对象时,你得到的可能是NSLiteralString,NSCFString,NSSimpleCString等。即不同的NSString对象调用同一个接口A,接口A的实现可能是不同的。

[NSMutableArray new] 实际上返回的是哪个类的实例呢?

答:__NSArrayM

ivars 的意思

我们来概括下每个 ivar 的意思:

  • _used 是计数的意思
  • _list 是缓冲区指针
  • _size 是缓冲区的大小
  • _offset 是在缓冲区里的数组的第一个元素索引
数据结构

__NSArrayM 用了环形缓冲区 (circular buffer)。这个数据结构相当简单,只是比常规数组或缓冲区复杂点。环形缓冲区的内容能在到达任意一端时绕向另一端。

环形缓冲区有一些非常酷的属性。尤其是,除非缓冲区满了,否则在任意一端插入或删除均不会要求移动任何内存。我们来分析这个类如何充分利用环形缓冲区来使得自身比 C 数组强大得多。在任意一端插入或者删除,只是修改offset参数,不需要移动内存,我们访问的时候只是不和普通的数组一样index多少就是多少,这里会计算加上offset之后处理的值取数据,而不是插入头和尾巴的时候,环形结构会根据最少移动内存指针的方式插入,例如要在A和B之间插入,按照C的数组,我们需要把B到E的元素移动内存,但是环形缓冲区的设计,我们只要把A的值向前移动一个单位内存,即可,同时修改offset偏移量,就能保证最小的移动单元来完成中间插入。

在两端插入或删除会相当地快,最糟糕的就是中间插入和删除中间。

子类

任何 NSMutableArray 的子类只须实现 7 个最基本的方法。所有其它高等级的抽象建立在它们的基础之上

  • - count
  • - objectAtIndex:
  • - insertObject:atIndex:
  • - removeObjectAtIndex:
  • - addObject:
  • - removeLastObject
  • - replaceObjectAtIndex:withObject:

CFArray

当 CF 类可用时,不需要重新发明 实现了全新的 NS 类的轮子。

NSArrayNSMutableArrayCFArray 完全没有共同点。

CFArray 和 _NSArrayM 异同点

CFArray 不使用环形缓冲区,反而使用一个两端都填充着零的更大的缓冲区,使得枚举和获取正确的对象变得更加简单。在任意一端添加元素只是简单地吃到剩余的被填充的内存。

这两者有什么共同点?它们是像双向队列这类抽象数据类型的具体实现。不管它的名字,NSMutableArray 是一个类固醇数组,剥除了 C 风格数组对应的缺点。

总结

  • NSMutableArray 是一个高级抽象数组,解决了 C 风格数组对应的缺点。(C数组插入的时候都会移动内存,不是O(1),用到了环形缓冲区数据结构来处理内存移动的损耗)。
  • 可变数组任意一端插入或删除能有固定时间的性能。而且在中间插入和删除的时候都会试着去移动最小化内存。
  • 环形缓冲区的数据结构如果是连续数组结构,在扩容的时候难免会移动大量内存,因此用链表实现环形缓冲会更好

参考

NSMutableArray原理揭露

NSDictionary和NSMutableArray底层原理(哈希表和环形缓冲区)

最近的文章

通过fishhook探寻iOS动态加载过程

本文通过对 fishhook 运行原理的分析,来探寻一个 iOS 应用在动态加载的过程中究竟做了哪些操作。 …

, , , 开始阅读
更早的文章

2018 年终总结

2018 年终总结,记录在今年的最后一天工作日。本来只想发个沸点,但是写着写着发现字数太多了… ⇎_⇎ 总感觉是,碌碌无为。 关于工作: 年中的时候,送走了合作一年多的老搭档,一个月后迎来了一位新搭档,嗯,三个月后又送走了她。 年初开发沸点的时候,遇到性能瓶颈,开发周期也非常紧张,做了个冒险的决 …

, 开始阅读
comments powered by Disqus