iOS总结-NSArray的底层实现


有关NSArray的

不管是NSArray,还是nsmutable ,alloc之后的得到都是__NSPlacrholderArray.
当我们nsarray一个空数组,得到的是__NSArray0
nsarray只有一个元素时,得到的是__NSSingleObjectArrayI
nsarray.count > 1 时, 得到 __NSArrayI
nsmutablearray 返回的都是__NSArrayM
placeHolder 和 placeHoldes 的内存地址一样,说明是一个单例,该类内部只有一个isa指针,init后被新的实例换掉了

CFArray 是CoreFoundation中的, 和Foundation中的NSArray相对应,他们是Toll-Free-Briaged. 用的环形缓冲区实现的.

C数组的原理  连续的内存空间, 在下标0处插入一个元素时, 移动其后面所有的元素, 即memmove原理

同样的移除第一个元素,需要进行相同的动作

 

当数组非常大时,就有问题了,NSMutableArray使用环形缓冲区,_NSArrayM用了环形缓冲区(circular buffer),这个数据结构相对简单,只是比常规数组/缓冲区复杂点.环形缓冲区的内容能在到达任意一段时绕向另一端.

环形缓冲区,在删除的时候不会清楚指针, 如果我们在中间进行插入和删除, 只会移动最少的一边元素.

遍历数组的几个方法:

for循环   NSEnumerator   forin   enumerateObjectsUsingBlock:(通过block回调,在子线程中遍历,对象的回调次序是乱序的,而且调用线程会等待该遍历过程完成:)  
这几个 forin性能最好,for循环较低, 多线程遍历方式是性能最差的.

__NSArrayI{
    NSInterger _userd;  数组的元素个数,调用[array count]时,返回的就是_userd的值。
     id_list[0]; 当做id_list来用,即一个存储id对象的buff.由于__NSArrayI的不可变,所以_list一旦分配,释放之前都不会再有移动删除操作了。
}
__NSArrayI的实现

 __NSArrayI对这个方法的实现中,主要把内部数组的_list赋给state->itemsPtr,并返回_used数组大小。state->mutationsPtr指向一个局部静态变量,state->state看起来是一个标志,再次用同一个state调用这个方法就直接返回0.
      快速枚举的意思就是一下就把全部对象获取到了,而且在一个c数组里,之后要获得哪个位置的对象都可以快速寻址到,通过state->itemsPtr来访问数组。
__NSSingleObjectArrayI{
id object; 因为只有在创建只包含一个对象的不可变数组时,才会得到__NSSingleObjectArrayI对象,所以其内部结构更加简单
}
__NSArrayM{  它的内部对象数组时一块连续内存id *_list
NSUInterger _used;  当前对象数目  [nsmutablearray count]
NSUInterger _offset;  时机对象数组的起始偏移
int_size: 28;  已分配的_list大小,能存储的对象个数,不是字节数
int_unused: 4; 
uint32_t _mutations;修改标记,每次对__NSArrayM的修改操作都会使_mutations +1  "Collection <__NSArrayM: 0x1002076b0> was mutated while being enumerated" 这个异常就是通过对_mutations的识别来引发的。
id *_list;是个循环数组,并且在增删操作时会动态地重新分配以符合当前的存储需求  
}
__NSArrayM的实现

从实现来看,如果_list还没有构成循环,第一次就获得了全部元素,跟__NSArrayI一样。但是如果_list构成了玄幻,就需要两次,第一次获取_offset到_list末端的元素,第二次获取存放在_list起始处的剩余元素。

__NSArrayM的_list是个循环数组,它的其实由_offset标识.

forin速度最快的原因是遵从了NSFastEnumertation协议,它是直接从C数组中去对象对于可变数组来说,最多只需要两次就可以获取全部数据。如果数组没有构成循环,第一次就获得了全部元素,跟不可变数组一样,如果数组构成了循环,那么就需要两次,第一次获取对象数组的起始偏移到循环数组末端的元素,第二次获取存放在循环数组起始处的剩余元素。而for循环之所以慢一点,是每次都要调用objectAtIndex:,添加@autoreleasepool,可以提高效率,如果我们每次遍历不需要知道下标,选择forin。

forin是基于快速枚举实现的,编译器将for in 转化为两层循环,外层调用快速枚举方法批量获取元素,内层通过c数组取得一批元素中的每一个,并且在每次获取元素前,检查是否对数组对象进行了变更操作,如果是,则抛出异常。
block循环,系统已经帮我加了@autoreleasepool,其他的循环可以通过@autoreleasepool来优化。

NSEnumerationConcurrent+Block的方式耗时最大,我认为是因为它采用多线程,就这个方法来京,多线程的优势并不在遍历多快,它的回调在各个子线程。

参考https://www.sohu.com/a/235998120_208051
https://www.jianshu.com/p/66f8410c6bbc

猜你喜欢

转载自blog.csdn.net/qq_28551705/article/details/84997087
今日推荐