在iOS開發(fā)中,廣泛運(yùn)用了類蔟(Class clusters)的設(shè)計(jì)模式幕随。如NSNumber蚁滋、NSString、NSArray等赘淮。類簇其實(shí)是對(duì)現(xiàn)實(shí)的一種抽象和封裝辕录,基于抽象工廠模式(Abstract Factory Pattern)。最近在讀書過程中聯(lián)想到一些東西梢卸,于是嘗試更加深入地去了解它走诞。
問題
所謂抽象工廠模式就是將各種同一主題的工廠類封裝起來,提供一個(gè)通用的抽象工廠類而不用知道具體的工廠類低剔。對(duì)于類蔟的論述已經(jīng)多如牛毛了速梗,在此更加推薦閱讀蘋果的官方文檔肮塞。本篇文章將以NSArray為例,著重講下從alloc到init過程中發(fā)生的事姻锁。在重溫《Effective Objective-C 2.0》的過程中我注意到這么一段話:
In the case of NSArray, when an instance is allocated, it’s an instance of another class that’s allocated (during a call to alloc), known as a placeholder array. This placeholder array is then converted to an instance of another class, which is a concrete subclass of NSArray. This is a pretty little dance but beyond the scope of this book to explan fully.
可以用一個(gè)具體的例子來說明枕赵,比如:
NSArray *placeholder = [NSArray alloc];
NSArray *arr1 = [placeholder init];
NSArray *arr2 = [placeholder initWithObjects:@0, nil];
NSArray *arr3 = [placeholder initWithObjects:@0, @1, nil];
NSArray *arr4 = [placeholder initWithObjects:@0, @1, @2, nil];
NSLog(@"placeholder: %s", object_getClassName(placeholder)); // placeholder: __NSPlaceholderArray
NSLog(@"arr1: %s", object_getClassName(arr1)); // arr1: __NSArray0
NSLog(@"arr2: %s", object_getClassName(arr2)); // arr2: __NSSingleObjectArrayI
NSLog(@"arr3: %s", object_getClassName(arr3)); // arr3: __NSArrayI
NSLog(@"arr4: %s", object_getClassName(arr4)); // arr4: __NSArrayI
可以看到,alloc后所得到的類為__NSPlaceholderArray位隶。而當(dāng)init為一個(gè)空數(shù)組后拷窜,變成了__NSArray0。如果有且僅有一個(gè)元素涧黄,那么為__NSSingleObjectArrayI篮昧。如果數(shù)組大于一個(gè)元素,那么為__NSArrayI笋妥。這兒暫且不去討論為什么arr1-4有所區(qū)別——先來關(guān)心一下為什么alloc和init前后轉(zhuǎn)化為了不同的類懊昨。
從名字上很容易知道__NSPlaceholderArray作用為占位,我們可以嘗試打印幾個(gè)地址:
NSArray *placeholder = [NSArray alloc];
NSArray *placeholder2 = [NSArray alloc];
NSArray *arr1 = [placeholder init];
NSArray *arr2 = [placeholder initWithObjects:@0, nil];
NSLog(@"placeholder: %p", placeholder); // placeholder: 0x618000013b10
NSLog(@"placeholder2: %p", placeholder2); // placeholder2: 0x618000013b10
NSLog(@"arr1: %p", arr1); // arr1: 0x618000013b30
NSLog(@"arr2: %p", arr2); // arr2: 0x608000014050
可以看到[NSArray alloc]
產(chǎn)生的實(shí)例為一個(gè)單例春宣,而在init或者其他初始化方法后酵颁,地址發(fā)生了變化,也就是說月帝,placeholder目前看來只是一個(gè)占位用的單例躏惋,在init后即被新的實(shí)例給替換掉了。那么嚷辅,這個(gè)placeholder真的只用做占位嗎簿姨?
__NSPlaceholderArray
我們可以參考另一個(gè)開源實(shí)現(xiàn)GNUstep一瞥究竟。根據(jù)GNUstep的代碼簸搞,可知NSObject的alloc是直接返回的[self allocWithZone: NSDefaultMallocZone()]
扁位,也就是說調(diào)用了對(duì)應(yīng)類實(shí)現(xiàn)的此方法。我們來看看GNUstep中NSArray的allocWithZone:
是如何實(shí)現(xiàn)的:
+ (id) allocWithZone: (NSZone*)z
{
if (self == NSArrayClass)
{
/*
* For a constant array, we return a placeholder object that can
* be converted to a real object when its initialisation method
* is called.
*/
if (z == NSDefaultMallocZone() || z == 0)
{
/*
* As a special case, we can return a placeholder for an array
* in the default malloc zone extremely efficiently.
*/
return defaultPlaceholderArray;
}
else
{
// 此處省略
}
}
else
{
return NSAllocateObject(self, 0, z);
}
}
可以看到NSArray此時(shí)會(huì)返回defaultPlaceholderArray攘乒。在GNUstep的實(shí)現(xiàn)中贤牛,defaultPlaceholderArray實(shí)例所對(duì)應(yīng)的類為GSPlaceholderArray。所以alloc完成后的init
消息是發(fā)送給GSPlaceholderArray實(shí)例的则酝。而init
恰恰調(diào)用的是initWithObjects:count:
——這個(gè)方法其實(shí)就是NSArray的指定初始化方法殉簸。我們繼續(xù)看看GNUstep實(shí)現(xiàn):
// GSPlaceholderArray
- (id) initWithObjects: (const id[])objects count: (NSUInteger)count
{
self = (id)NSAllocateObject(GSInlineArrayClass, sizeof(id)*count, [self zone]);
return [self initWithObjects: objects count: count];
}
// GSInlineArray
- (id) initWithObjects: (const id[])objects count: (NSUInteger)count
{
_contents_array = (id*)(((void*)self) + class_getInstanceSize([self class]));
if (count > 0)
{
NSUInteger i;
for (i = 0; i < count; i++)
{
if ((_contents_array[i] = RETAIN(objects[i])) == nil)
{
_count = i;
DESTROY(self);
[NSException raise: NSInvalidArgumentException format: @"Tried to init array with nil object"];
}
}
_count = count;
}
return self;
}
可以看到在GSPlaceholderArray的initWithObjects:count:
方法中,通過NSAllocateObject給GSInlineArray實(shí)例分配空間沽讹,包括所包含元素的空間般卑。并且在GSInlineArray的initWithObjects:count:
方法中,對(duì)分配的元素的空間進(jìn)行初始化爽雄。自此就返回了一個(gè)類型為GSInlineArray的實(shí)例蝠检。
CoreFoundation中NSArray的相關(guān)實(shí)現(xiàn)會(huì)比GNUstep中的實(shí)現(xiàn)復(fù)雜些,但通過匯編代碼來看可以知道基本邏輯是類似的挚瘟,在此不再贅述叹谁。有幾點(diǎn)可以提下:1饲梭、當(dāng)元素為空時(shí),返回的是__NSArray0的單例焰檩;2憔涉、當(dāng)元素僅有一個(gè)時(shí),返回的是__NSSingleObjectArrayI的實(shí)例析苫;3兜叨、當(dāng)元素大于一個(gè)的時(shí)候,返回的是__NSArrayI的實(shí)例衩侥。根據(jù)網(wǎng)上的資料国旷,大多未提及__NSSingleObjectArrayI,可能是后面新增的茫死,理由大概還是為了效率跪但,在此不深究。
同樣的峦萎,對(duì)于NSMutableArray特漩、NSNumber、NSString等也是有相同的NSPlaceholderNumber機(jī)制的骨杂。
可變類的Placeholder
提到NSMutableArray,那么問題來了——NSMutableArray是否也有NSMutablePlaceholderArray呢雄卷?
答案是:并沒有搓蚪。一開始我也是先入為主地認(rèn)為一定對(duì)應(yīng)著一個(gè)可變類型的placeholderArray。但在好奇心驅(qū)使下打印了各個(gè)實(shí)例的父類后丁鹉,我吃驚的發(fā)現(xiàn)其實(shí)并沒有——它依然是__NSPlaceholderArray妒潭。
NSArray *placeholder = [NSArray alloc];
NSArray *arr1 = [placeholder init];
NSArray *arr2 = [placeholder initWithObjects:@0, nil];
NSArray *arr3 = [placeholder initWithObjects:@0, @1, nil];
NSLog(@"superclass of placeholder: %s", class_getName(placeholder.superclass)); // superclass of placeholder: NSMutableArray
NSLog(@"superclass of arr1: %s", class_getName(arr1.superclass)); // superclass of arr1: NSArray
NSLog(@"superclass of arr2: %s", class_getName(arr2.superclass)); // superclass of arr2: NSArray
NSLog(@"superclass of arr3: %s", class_getName(arr3.superclass)); // superclass of arr3: NSArray
NSMutableArray *mPlaceholder = [NSMutableArray alloc];
NSMutableArray *mArr1 = [mPlaceholder init];
NSMutableArray *mArr2 = [mPlaceholder initWithObjects:@0, nil];
NSMutableArray *mArr3 = [mPlaceholder initWithObjects:@0, @1, nil];
NSLog(@"mPlaceholder: %s", object_getClassName(mPlaceholder)); // mPlaceholder: __NSPlaceholderArray
NSLog(@"mArr1: %s", object_getClassName(mArr1)); // mArr1: __NSArrayM
NSLog(@"mArr2: %s", object_getClassName(mArr2)); // mArr2: __NSArrayM
NSLog(@"mArr3: %s", object_getClassName(mArr3)); // mArr3: __NSArrayM
NSLog(@"superclass of mPlaceholder: %s", class_getName(mPlaceholder.superclass)); // superclass of mPlaceholder: NSMutableArray
NSLog(@"superclass of mArr1: %s", class_getName(mArr1.superclass)); // superclass of mArr1: NSMutableArray
NSLog(@"superclass of mArr2: %s", class_getName(mArr2.superclass)); // superclass of mArr2: NSMutableArray
NSLog(@"superclass of mArr3: %s", class_getName(mArr3.superclass)); // superclass of mArr3: NSMutableArray
當(dāng)時(shí)我的心里大概出現(xiàn)了這么個(gè)文件名:大吃一驚.jpg。但轉(zhuǎn)念一想也是可以接受的揣钦,畢竟NSMutableArray是NSArray的子類雳灾,從這個(gè)角度來看,共用一個(gè)NSPlaceholderArray也是情有可原的冯凹。那么現(xiàn)在的問題是:它是個(gè)單例谎亩,又該怎么區(qū)分可變和不可變數(shù)組的呢?畢竟兩個(gè)初始化方法selector是相同的宇姚。GNUstep似乎并不能找到答案匈庭,那么就再次祭出大殺器匯編吧。
CoreFoundation`-[__NSPlaceholderArray initWithObjects:count:]:
; 前略
0x10edf9698 <+40>: je 0x10edf96b3 ; <+67>
0x10edf969a <+42>: nopw (%rax,%rax)
0x10edf96a0 <+48>: cmpq $0x0, (%rdx,%r8,8)
0x10edf96a5 <+53>: je 0x10edf972c ; <+188>
0x10edf96ab <+59>: incq %r8
0x10edf96ae <+62>: cmpq %r9, %r8
0x10edf96b1 <+65>: jb 0x10edf96a0 ; <+48>
-> 0x10edf96b3 <+67>: cmpq %rdi, 0x3b514e(%rip) ; __immutablePlaceholderArray
0x10edf96ba <+74>: je 0x10edf96d2 ; <+98>
-> 0x10edf96bc <+76>: cmpq %rdi, 0x3b514d(%rip) ; __mutablePlaceholderArray
0x10edf96c3 <+83>: jne 0x10edf97b7 ; <+327>
0x10edf96c9 <+89>: movq 0x3aa260(%rip), %rdi ; (void *)0x000000010f1a5db0: __NSArrayM
0x10edf96d0 <+96>: jmp 0x10edf9717 ; <+167>
0x10edf96d2 <+98>: cmpq $0x1, %r9
0x10edf96d6 <+102>: je 0x10edf96f5 ; <+133>
0x10edf96d8 <+104>: testq %r9, %r9
0x10edf96db <+107>: jne 0x10edf9710 ; <+160>
0x10edf96dd <+109>: leaq 0x3b7c9c(%rip), %rax ; __NSArray0__
0x10edf96e4 <+116>: movq (%rax), %rdi
0x10edf96e7 <+119>: movq 0x3a862a(%rip), %rsi ; "retain"
0x10edf96ee <+126>: popq %rbp
0x10edf96ef <+127>: jmpq *0x371b2b(%rip) ; (void *)0x000000010e961ac0: objc_msgSend
0x10edf96f5 <+133>: movq 0x3aa224(%rip), %rdi ; (void *)0x000000010f1a5d60: __NSSingleObjectArrayI
0x10edf96fc <+140>: movq (%rdx), %rdx
0x10edf96ff <+143>: movq 0x3a92c2(%rip), %rsi ; "__new::"
0x10edf9706 <+150>: xorl %ecx, %ecx
0x10edf9708 <+152>: callq *0x371b12(%rip) ; (void *)0x000000010e961ac0: objc_msgSend
0x10edf970e <+158>: popq %rbp
0x10edf970f <+159>: retq
0x10edf9710 <+160>: movq 0x3aa211(%rip), %rdi ; (void *)0x000000010f1a5d88: __NSArrayI
0x10edf9717 <+167>: movq 0x3a92b2(%rip), %rsi ; "__new:::"
0x10edf971e <+174>: xorl %r8d, %r8d
0x10edf9721 <+177>: movq %r9, %rcx
0x10edf9724 <+180>: callq *0x371af6(%rip) ; (void *)0x000000010e961ac0: objc_msgSend
0x10edf972a <+186>: popq %rbp
; 后也略
讓我們重點(diǎn)關(guān)注兩個(gè)箭頭所指向的cmpq指令吧浑劳≮宄郑可以很清楚地知道,其實(shí)就是判斷self == __immutablePlaceholderArray和self == __mutablePlaceholderArray魔熏。也就是說衷咽,CoreFoundation在某個(gè)時(shí)機(jī)初始化了兩個(gè)NSPlaceholderArray鸽扁,分別存起來。在調(diào)用__NSPlaceholderArray的initWithObjects:count:
方法時(shí)镶骗,直接通過判斷存起來的這兩個(gè)單例來判斷是否是不可變還是可變數(shù)組桶现。真相就是這么赤裸裸的簡(jiǎn)單粗暴。
我們?cè)賮砜纯?code>+[NSArray allocWithZone:]
CoreFoundation`+[NSArray allocWithZone:]:
0x10b5004a0 <+0>: pushq %rbp
0x10b5004a1 <+1>: movq %rsp, %rbp
0x10b5004a4 <+4>: pushq %r15
0x10b5004a6 <+6>: pushq %r14
0x10b5004a8 <+8>: pushq %rbx
0x10b5004a9 <+9>: subq $0x18, %rsp
0x10b5004ad <+13>: movq %rdx, %r14
0x10b5004b0 <+16>: movq %rdi, %rbx
0x10b5004b3 <+19>: movq 0x3aa47e(%rip), %rdi ; (void *)0x000000010b8acdd8: NSArray
0x10b5004ba <+26>: movq 0x3a9647(%rip), %r15 ; "self"
0x10b5004c1 <+33>: movq %r15, %rsi
0x10b5004c4 <+36>: callq *0x371d56(%rip) ; (void *)0x000000010b068ac0: objc_msgSend
-> 0x10b5004ca <+42>: cmpq %rbx, %rax
0x10b5004cd <+45>: je 0x10b500511 ; <+113>
0x10b5004cf <+47>: movq 0x3aa392(%rip), %rdi ; (void *)0x000000010b8ace50: NSMutableArray
0x10b5004d6 <+54>: movq %r15, %rsi
0x10b5004d9 <+57>: callq *0x371d41(%rip) ; (void *)0x000000010b068ac0: objc_msgSend
-> 0x10b5004df <+63>: cmpq %rbx, %rax
0x10b5004e2 <+66>: je 0x10b500521 ; <+129>
0x10b5004e4 <+68>: movq %rbx, -0x28(%rbp)
0x10b5004e8 <+72>: movq 0x3aa7e9(%rip), %rax ; (void *)0x000000010b8acea0: NSArray
0x10b5004ef <+79>: movq %rax, -0x20(%rbp)
0x10b5004f3 <+83>: movq 0x3a88b6(%rip), %rsi ; "allocWithZone:"
0x10b5004fa <+90>: leaq -0x28(%rbp), %rdi
0x10b5004fe <+94>: movq %r14, %rdx
0x10b500501 <+97>: callq 0x10b6acb50 ; symbol stub for: objc_msgSendSuper2
0x10b500506 <+102>: addq $0x18, %rsp
0x10b50050a <+106>: popq %rbx
0x10b50050b <+107>: popq %r14
0x10b50050d <+109>: popq %r15
0x10b50050f <+111>: popq %rbp
0x10b500510 <+112>: retq
0x10b500511 <+113>: movq 0x3aa428(%rip), %rdi ; (void *)0x000000010b8ace78: __NSPlaceholderArray
0x10b500518 <+120>: movq 0x3a94d9(%rip), %rsi ; "immutablePlaceholder"
0x10b50051f <+127>: jmp 0x10b50052f ; <+143>
0x10b500521 <+129>: movq 0x3aa418(%rip), %rdi ; (void *)0x000000010b8ace78: __NSPlaceholderArray
0x10b500528 <+136>: movq 0x3a94f1(%rip), %rsi ; "mutablePlaceholder"
0x10b50052f <+143>: addq $0x18, %rsp
0x10b500533 <+147>: popq %rbx
0x10b500534 <+148>: popq %r14
0x10b500536 <+150>: popq %r15
0x10b500538 <+152>: popq %rbp
0x10b500539 <+153>: jmpq *0x371ce1(%rip) ; (void *)0x000000010b068ac0: objc_msgSend
0x10b50053f <+159>: nop
依舊看兩個(gè)箭頭卖词,可以看到當(dāng)self為NSArray和NSMutableArray時(shí)候分別返回immutablePlaceholder和mutablePlaceholder巩那,它們都是__NSPlaceholderArray類型的。這樣就驗(yàn)證了上面的想法此蜈。
Primitive methods
上面多處提到了initWithObjects:count:
即横。為什么它這么重要?我們可以看看NSArray的interface是如何定義的:
<figure class="highlight" style="background: rgb(255, 255, 255);">
@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
@property (readonly) NSUInteger count;
- (ObjectType)objectAtIndex:(NSUInteger)index;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects count:(NSUInteger)cnt NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
@end
不同于普通的繼承裆赵,在創(chuàng)建某個(gè)類蔟的具體的子類時(shí)东囚,通常不需要實(shí)現(xiàn)所有的功能。也不同于普通的抽象類战授,在公共的抽象基類中页藻,一般提供了輔助的方法的實(shí)現(xiàn),子類只需要提供幾個(gè)核心方法的實(shí)現(xiàn)即可植兰。
在CoreFoundation的類蔟的抽象工廠基類(如NSArray份帐、NSString、NSNumber等)中楣导,Primitive methods指的就是這些核心的方法废境,也就是那些在創(chuàng)建子類時(shí)必須要重寫的方法,通常在類的interface中聲明筒繁,在文檔中一般也會(huì)說明噩凹。其他可選實(shí)現(xiàn)的方法在Category中聲明。同時(shí)還需要注意其整個(gè)繼承樹的祖先的Primitive methods也都需要實(shí)現(xiàn)毡咏。