专注、坚持

iOS 中的数组

2020.07.21 by kingcos

Preface

数组,是我们在开发中经常使用的数据结构,其使用一段连续的内存空间存储。在 Obj-C 中,数组的类型分为 NSArray 不可变数组

Objective-C

NSArray & NSMutableArray

我们都知道,NSArray 是 Obj-C 中的不可变数组,而 NSMutableArray 是 Obj-C 的可变数组。这里我将 Obj-C 中几乎所有可能涉及到的数组对象,进行尝试打印其类别信息:

NSLog(@"%@", [[NSMutableArray alloc] class]);                                 // __NSPlaceholderArray
NSLog(@"%@", [[NSArray alloc] class]);                                        // __NSPlaceholderArray
NSLog(@"%@", [[[NSArray alloc] class] superclass]);                           // NSMutableArray
NSLog(@"%@", [[[[NSArray alloc] class] superclass] superclass]);              // NSArray
NSLog(@"%@", [[[[[NSArray alloc] class] superclass] superclass] superclass]); // NSObject

NSLog(@"%@", [[NSArray array] class]);                                        // __NSArray0
NSLog(@"%@", [[[NSArray alloc] init] class]);                                 // __NSArray0
NSLog(@"%@", [[[[NSArray alloc] init] class] superclass]);                    // NSArray

NSLog(@"%@", [[NSMutableArray array] class]);                                 // __NSArrayM
NSLog(@"%@", [[[NSMutableArray array] class] superclass]);                    // NSMutableArray

NSLog(@"%@", [@[@1] class]);                                                  // __NSSingleObjectArrayI
NSLog(@"%@", [[@[@1] class] superclass]);                                     // NSArray

NSLog(@"%@", [@[@1, @2] class]);                                              // __NSArrayI
NSLog(@"%@", [[@[@1, @2] class] superclass]);                                 // NSArray

NSMutableArray *arr = [NSMutableArray array];
NSLog(@"%@", [arr class]);                                                    // __NSArrayM

[arr addObject:@1];
NSLog(@"%@", [arr class]);                                                    // __NSArrayM

由以上结果,我们可得出下图的继承树:

1

显而易见,NSArrayNSMutableArray 均属于类簇(Class Cluster),Obj-C 中所广泛使用的一种抽象工厂设计模式。这种设计使得类型对外暴露的接口得到统一,隐藏内部的具体实现类。

__NSPlacehodlerArray

__NSPlacehodlerArray 顾名思义即「占位」数组。无论我们要创建 NSArray 还是 NSMutableArray,当调用 alloc 时,均返回了 __NSPlaceholderArray 类型的对象:

NSLog(@"%@", [[NSMutableArray alloc] class]); // __NSPlaceholderArray
NSLog(@"%@", [[NSArray alloc] class]);        // __NSPlaceholderArray

而每次使用的 __NSPlaceholderArray

id arr1 = [NSArray alloc];
id arr2 = [NSArray alloc];
id arr3 = [NSArray alloc];
id arr4 = [NSArray alloc];
id arr5 = [NSArray alloc];

id marr1 = [NSMutableArray alloc];
id marr2 = [NSMutableArray alloc];
id marr3 = [NSMutableArray alloc];
id marr4 = [NSMutableArray alloc];
id marr5 = [NSMutableArray alloc];

// 0x7fff90a905b8 - 0x7fff90a905b8 - 0x7fff90a905b8 - 0x7fff90a905b8 - 0x7fff90a905b8
NSLog(@"%p - %p - %p - %p - %p", arr1, arr2, arr3, arr4, arr5);
// 0x7fff90a905c8 - 0x7fff90a905c8 - 0x7fff90a905c8 - 0x7fff90a905c8 - 0x7fff90a905c8
NSLog(@"%p - %p - %p - %p - %p", marr1, marr2, marr3, marr4, marr5);

// 0x7fff90a905c8 - 0x7fff90a905b8 == 16

那么问题来了,__NSPlaceholderArray 内部是如何区分将要初始化的数组类型呢?

For historical reasons, alloc invokes allocWithZone:.

根据官方文档中的提示,alloc 方法由于历史原因,内部仍然调用了 allocWithZone:.。我们通过 LLDB breakpoint set --name "+[NSArray allocWithZone:]" 打个断点

CoreFoundation`+[NSArray allocWithZone:]:
->  0x7fff38ecb971 <+0>:   pushq  %rbp
    ...
    0x7fff38ecb9e8 <+119>: jmp    0x7fff38ecba0c            ; __NSArrayImmutablePlaceholder
    ...
    0x7fff38ecba02 <+145>: jmp    0x7fff38ee4a8d            ; __NSArrayMutablePlaceholder

CoreFoundation`__NSArrayImmutablePlaceholder:
->  0x7fff38ecba0c <+0>: leaq   0x57bc4ba5(%rip), %rax    ; ___immutablePlaceholderArray
    0x7fff38ecba13 <+7>: retq

CoreFoundation`__NSArrayMutablePlaceholder:
->  0x7fff38ee4a8d <+0>: leaq   0x57babb34(%rip), %rax    ; ___mutablePlaceholderArray
    0x7fff38ee4a94 <+7>: retq

Swift

Array

Reference