本文源碼基于objc4-781, macOS 10.15.6, Xcode Version 11.7 (11E801a)
在開發(fā)過程中我們會(huì)經(jīng)常使用到 + alloc 和 - init 方法,但是一直沒有深入探究過。這部分代碼其實(shí)蘋果是開源的,我們可以直接通過源碼來探索他們的底層實(shí)現(xiàn)。
alloc 方法分析
id _objc_rootAlloc(Class cls)
└── static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
└── id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
└── static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct, size_t *outAllocatedSize)
├── size_t instanceSize(size_t extraBytes)
├── void *calloc(size_t, size_t)
└── inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
上面就是調(diào)用 + alloc 方法的調(diào)用棧,這里只展示了關(guān)鍵部分的代碼。
callAlloc
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
cls->ISA()->hasCustomAWZ(): 是否重寫了+ alloc 或 + allocWithZone 方法。
如果沒有重寫 + alloc 或 + allocWithZone 就會(huì)調(diào)用 _objc_rootAllocWithZone ,否則會(huì)執(zhí)行消息轉(zhuǎn)發(fā)流程。
-
slowpath(x)和fastpath(x)
在條件判斷語句中如果使用了 fastpath(x), 編譯器會(huì)將在它之后的代碼塊緊接著前面的代碼,而將slowpath(x)后的代碼塊放在更遠(yuǎn)的位置,這樣能夠減少讀取代碼是由于指令跳轉(zhuǎn)帶來的性能損耗。因此我們一般將大概率執(zhí)行的條件放在 fastpath(x)中。
_class_createInstanceFromZone
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
// 計(jì)算對(duì)象的大小
size_t size = cls->instanceSize(extraBytes);
// 開辟內(nèi)存
id obj = (id)calloc(1, size);
// 初始化cls信息
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}
上面代碼只寫出了關(guān)鍵部分,首先計(jì)算出對(duì)象需要開辟的內(nèi)存空間的大小,然后開辟內(nèi)存空間,此時(shí)開辟出來的內(nèi)存并沒有任何內(nèi)容,因此我們需要初始化isa 。
- 字節(jié)對(duì)齊:有一點(diǎn)需要注意的是為對(duì)象分配的內(nèi)存大小是
16bytes對(duì)齊的, 且最小為16btyes。在以前的版本中是8bytes字節(jié)對(duì)齊的。
但是在通過可調(diào)式的源碼跟蹤對(duì)象創(chuàng)建的過程,發(fā)現(xiàn)了我們調(diào)用 [Person alloc] 時(shí),此時(shí)并不會(huì)直接執(zhí)行,而是先調(diào)用了 objc_alloc 方法, 方法實(shí)現(xiàn)如下:
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
file: Source/NSObject.mm line: 1726
上面的注釋是源碼中蘋果提供的,在調(diào)用alloc的方法時(shí),確實(shí)會(huì)調(diào)用此方法。
接著調(diào)用callAlloc, 注意這里的第三個(gè)參數(shù)傳的是 false 。在callAlloc方法中,判斷條件!cls->ISA()->hasCustomAWZ()為 false 且 allocWithZone 也為 false,此時(shí)會(huì)執(zhí)行 return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); 進(jìn)入消息轉(zhuǎn)發(fā)流程。
在消息轉(zhuǎn)發(fā)的過程中會(huì)在查找當(dāng)前cls和它的父類是否有重新+ alloc 或 + allocWithZone: 方法,如果有重寫,那么 !cls->ISA()->hasCustomAWZ() 依然為 false, 否則此條件就為 true。
在消息轉(zhuǎn)發(fā)流程中,最后會(huì)執(zhí)行 +[NSObject alloc] 方法,這樣就回到我們最初探討的流程了。
這里我們來看一下 _objc_rootAlloc 方法的實(shí)現(xiàn):
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
我們注意到這里第三個(gè)參數(shù)傳遞的是 true, 再次執(zhí)行 _objc_rootAlloc 方法,!cls->ISA()->hasCustomAWZ() 條件成立就會(huì)直接調(diào)用 _objc_rootAllocWithZone 方法, 如果不成立,接著往下執(zhí)行,此時(shí)的 allocWithZone 參數(shù)為 true 就會(huì)執(zhí)行 return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);, 開始 + allocWithZone: 的消息轉(zhuǎn)發(fā)流程。
最終會(huì)調(diào)用 +[NSObject allocWithZone:], 接著調(diào)用 _objc_rootAllocWithZone, 此時(shí)又回到了,我們開始分析的流程了。
總結(jié): 在調(diào)用 + alloc 方法時(shí), 底層實(shí)現(xiàn)并不是直接調(diào)用 + alloc 方法的,而是首先調(diào)用了 objc_alloc 方法,在對(duì)一個(gè)類第一次進(jìn)行初始化的時(shí)候 !cls->ISA()->hasCustomAWZ() 條件總是為 false, 然后進(jìn)入 alloc 的消息轉(zhuǎn)發(fā)流程, 同時(shí)查找當(dāng)前類和他的父類是否重寫了 + alloc 或 + allocWithZone: 方法,如果沒有找到那么 !cls->ISA()->hasCustomAWZ() 條件就會(huì)變?yōu)?true。 通過 +[NSObject alloc] 再次調(diào)用 callAlloc 方法,如果此時(shí) !cls->ISA()->hasCustomAWZ() 為 true 就會(huì)直接進(jìn)行初始化對(duì)象。
否則就會(huì)進(jìn)入+ allocWithZone: 的消息轉(zhuǎn)發(fā)流程,最終通過 +[NSObject allocWithZone] 進(jìn)行對(duì)象的創(chuàng)建。
筆者認(rèn)為蘋果這樣做是為了進(jìn)行性能優(yōu)化,在開發(fā)的過程中+ alloc 可能是我們調(diào)用最多的方法之一,如果一個(gè)類沒有重寫+ alloc 和 + allocWithZone: 方法,實(shí)際上它就沒有必要進(jìn)入消息轉(zhuǎn)發(fā)流程,可以直接進(jìn)行對(duì)象的內(nèi)存空間開辟,這樣做避免了消息轉(zhuǎn)發(fā)帶來的性能損耗。
init 方法分析
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
return obj;
}
init 方法實(shí)現(xiàn)就相當(dāng)?shù)暮?jiǎn)單,直接將self返回。這樣做的目的實(shí)際上就是為子類提供一個(gè)工廠方法,讓子類 做自定義的初始化操作。