NS(Mutable)Array的原理

2020-04-16  本文已影响0人  _zhw
OC数组的类体系

当我们创建一个NSArray对象时,实际上得到的是NSArray的子类__NSArrayI对象。同样的,当我们创建NSMutableArray对象时,实际上得到的是其子类__NSArrayM对象。不过,当我们创建空的或只有一个元素的NSArray对象时,得到的分别是__NSArray0__NSSingleObjectArrayI对象。

OC数组的类体系
为什么使用NSArrayNSMutableArray,却可以得到其子类?

这个机制可以总结为以下两大步骤
1.NSArray重写了+ (id)allocWithZone:(struct _NSZone *)zone方法。在方法内部,如果调用类为NSArray则直接返回全局变量___immutablePlaceholderArray;如果调用类为NSMutableArray则直接返回全局变量___mutablePlaceholderArray
也就是调用[NSArray alloc]或者[NSMutableArray alloc]得到的仅仅是两个占位指针,类型为__NSPlaceholderArray
2.在调用了alloc的基础上,不论是NSArrayNSMutableArray都必定要继续调用某个initXXX方法,而实际上调用的是__NSPlaceholderArrayinitXXX。在这个initXXX方法内部,如果self == ___immutablePlaceholderArray就会重新构造并返回__NSArrayI 对象,如果self == ___mutablePlaceholderArray就会重新构造并返回_NSArrayM对象。

总结来说,对于NSArrayNSMutableArrayalloc时拿到的仅仅是个占位对象,init后才得到真实的子类对象。

上面提到了一个私有类__NSPlaceholderArray,其内部包含有两个全局变量___immutablePlaceholderArray___mutablePlaceholderArray

OC数组的内部结构

NSArrayNSMutableArray,只是定义和实现了接口,而且对内部数据操作的接口都是在各个子类中实现的。所以真正需要了解的是其子类结构,了解__NSArrayI就相当于了解NSArray,了解__NSArrayM就相当于了解NSMutableArray

1.__NSArrayI

__NSArrayI的结构定义为:

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

@end

_used是数组的元素个数
_list[0]是OC数组内部实际存储对象的数组

2.__NSSingleObjectArrayI

__NSSingleObjectArrayI的结构定义为:

@interface __NSSingleObjectArrayI : NSArray {
    id object;
}

@end

因为只有一个元素,所以只需一个object即可。

3.__NSArrayM

__NSArrayM的结构定义为:

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

@end

_used是数组的元素个数
_offset实际存储对象的数组的起始偏移
_size已分配的_list大小
_mutations修改标记,每次对__NSArrayM的修改操作都会使_mutations加1
_list是个环形缓冲区,必要时会动态重新分配内存空间以符合当前存储需求

以一个初始包含5个元素,总大小_size为6的_list为例:
_offset = 0, _used = 5, _size = 6


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

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

删除对象E:
_offset = 2, _used = 6, _size = 8
B、C往后移动了,E的空缺被填补(移动最少元素的一边)

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

因此可见,__NSArrayM_list是个环形缓冲区,它的起始由_offset标识。

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

上一篇下一篇

猜你喜欢

热点阅读