OC数组的类体系
当我们创建一个NSArray
对象时,实际上得到的是NSArray的子类__NSArrayI
对象。同样的,当我们创建NSMutableArray
对象时,实际上得到的是其子类__NSArrayM
对象。不过,当我们创建空的或只有一个元素的NSArray
对象时,得到的分别是__NSArray0
或__NSSingleObjectArrayI
对象。
为什么使用
NSArray
和NSMutableArray
,却可以得到其子类?
这个机制可以总结为以下两大步骤:
1.NSArray
重写了+ (id)allocWithZone:(struct _NSZone *)zone
方法。在方法内部,如果调用类为NSArray
则直接返回全局变量___immutablePlaceholderArray
;如果调用类为NSMutableArray
则直接返回全局变量___mutablePlaceholderArray
。
也就是调用[NSArray alloc]
或者[NSMutableArray alloc]
得到的仅仅是两个占位指针,类型为__NSPlaceholderArray
。
2.在调用了alloc
的基础上,不论是NSArray
或NSMutableArray
都必定要继续调用某个initXXX
方法,而实际上调用的是__NSPlaceholderArray
的initXXX
。在这个initXXX
方法内部,如果self == ___immutablePlaceholderArray
就会重新构造并返回__NSArrayI
对象,如果self == ___mutablePlaceholderArray
就会重新构造并返回_NSArrayM
对象。
总结来说,对于NSArray
和NSMutableArray
,alloc
时拿到的仅仅是个占位对象,init
后才得到真实的子类对象。
上面提到了一个私有类:__NSPlaceholderArray
,其内部包含有两个全局变量___immutablePlaceholderArray
和___mutablePlaceholderArray
。
OC数组的内部结构
NSArray
和NSMutableArray
,只是定义和实现了接口,而且对内部数据操作的接口都是在各个子类中实现的。所以真正需要了解的是其子类结构,了解__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
网友评论