1.準(zhǔn)備工作
- 我的運(yùn)行環(huán)境
- macOS 13.1
- M1 芯片
- Xcode 14.0.1
- 首先準(zhǔn)備好一份源碼,鏈接地址:https://github.com/LGCooci/KCObjc4_debug ,我使用的是
objc4-866.9,初次運(yùn)行報(bào)錯(cuò)如下:

__dyld_get_dlopen_image_header 和__dyld_objc_register_callbacks報(bào)錯(cuò)。
解決辦法:分別搜一下這兩個(gè)方法改成如下


clean一下,再運(yùn)行就成功了。不過偶爾也報(bào)錯(cuò),但是再clean一次就可以了。
2.從alloc入手
LGPerson *p = [LGPerson alloc];
LGPerson * p1 = [p init];
LGPerson * p2 = [p init];
NSLog(@"對象p:%@,p的指針:%p",p,&p);
NSLog(@"對象p1:%@,p1的指針:%p",p1,&p1);
NSLog(@"對象p2:%@,p2的指針:%p",p2,&p2);
輸出:
對象p :<LGPerson: 0x600000c0c000>,p的指針: 0x30410b230
對象p1:<LGPerson: 0x600000c0c000>,p1的指針:0x30410b220
對象p2:<LGPerson: 0x600000c0c000>,p2的指針:0x30410b228
從輸出來看p,p1,p2指向的是一個(gè)對象,也就是同一片內(nèi)存,但是p,p1,p2本身是不同的。

alloc方法開辟了一塊內(nèi)存空間,下面的init方法相當(dāng)于又創(chuàng)建了幾個(gè)指針來指向這塊內(nèi)存。但是他們具體做了什么操作,需要進(jìn)一步的探究源碼,一探究竟。
3.探究源碼的方式
跟著KC學(xué)習(xí)到了探究源碼的三種方式。
- 使用
Control+Step Into進(jìn)行調(diào)試 - 通過匯編查看調(diào)用流程
- 通過已知符號(hào)來探索具體調(diào)用
通過這三種方式都可以找到最終的libobjc.A.dylib-objc_alloc
4.alloc的具體探究
打開我們準(zhǔn)備好的objc4-866.9,然后運(yùn)行代碼跟著流程和調(diào)用來看看一個(gè)alloc下面到底藏了多少方法。
4.1 alloc的實(shí)現(xiàn)方法
跟著源碼,點(diǎn)擊進(jìn)入到alloc的實(shí)現(xiàn):
+ (id)alloc {
return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
調(diào)用流程alloc->_objc_rootAlloc->callAlloc,而callAlloc的實(shí)現(xiàn)就相對比較復(fù)雜了。
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
// 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));
}
這里出現(xiàn)了幾個(gè)比較重要的判斷和方法:
1、slowpath和fastpath
這是宏定義
// 表示x的值為假的可能性更大
#define slowpath(x) (__builtin_expect(bool(x), 0))
// 表示x的值為真的可能性更大
#define fastpath(x) (__builtin_expect(bool(x), 1))
__builtin_expect是GCC提供給程序員使用,目的是將“分支轉(zhuǎn)移”的信息提供給編譯器,這樣編譯器可以對代碼進(jìn)行優(yōu)化,以減少指令跳轉(zhuǎn)帶來的性能下降。
if (slowpath(checkNil && !cls))的意思就是cls大概率是有值的,告訴編譯器編譯時(shí)優(yōu)化掉這個(gè)部分,也就是nil這個(gè)部分幾乎不會(huì)走,那么就直接來到了判斷if (fastpath(!cls->ISA()->hasCustomAWZ()))的部分。
2、hasCustomAWZ
它的實(shí)現(xiàn)如下:
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
而FAST_CACHE_HAS_DEFAULT_AWZ的描述是:
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14)
它是判斷這個(gè)類或者它的父類有沒有實(shí)現(xiàn)allocWithZone的,而方法hasCustomAWZ大概率全稱是hasCustomAllocWithZone后面AllocWithZone縮寫成了AWZ??。
而類本身是有懶加載的概念的,第一次給這個(gè)類發(fā)送消息之前,該類是沒有加載的,所以收到alloc消息的時(shí)候,那么allocWithZone是沒有默認(rèn)實(shí)現(xiàn)的,所以hasCustomAWZ返回false,會(huì)走流程_objc_rootAllocWithZone
第一次進(jìn)到該方法:

而方法_objc_rootAllocWithZone的具體實(shí)現(xiàn)接著看下面。
3、_objc_rootAllocWithZone
id
_objc_rootAllocWithZone(Class cls, objc_zone_t zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
調(diào)用了_class_createInstanceFromZone,這個(gè)方法就內(nèi)容挺長的還挺多的,下面來著重的看一下。
4.2 class_createInstanceFromZone方法開辟空間

- hasCxxCtor() 和 hasCxxDtor()
hasCxxCtor和hasCxxDtor是用來處理C++成員變量的構(gòu)造和析構(gòu)。
hasCxxCtor是判斷當(dāng)前class或者superclass是否有.cxx_construct的實(shí)現(xiàn);
hasCxxDtor是判斷當(dāng)前class或者superclass是否有.cxx_destruct的實(shí)現(xiàn)。
// class or superclass has .cxx_construct/.cxx_destruct implementation
// FAST_CACHE_HAS_CXX_DTOR is chosen to alias with isa_t::has_cxx_dtor
#define FAST_CACHE_HAS_CXX_CTOR (1<<1)
bool hasCxxCtor() {
ASSERT(isRealized());
return cache.getBit(FAST_CACHE_HAS_CXX_CTOR);
}
- instanceSize()
//extraBytes 傳進(jìn)來是0
inline size_t instanceSize(size_t extraBytes) const {
if(fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
//至少16個(gè)字節(jié)!?。。。。。。。。。。。。。。。。。。?!
if(size < 16) size = 16;
return size;
}
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
#ifdef __LP64__
#define WORD_SHIFT 3UL
#define WORD_MASK 7UL
#define WORD_BITS 64
#else
#define WORD_SHIFT 2UL
#define WORD_MASK 3UL
#define WORD_BITS 32
#endif
在調(diào)用方法instanceSize傳進(jìn)來的參數(shù)extraBytes是0;
hasFastInstanceSize在64位下才啟用;
alignedInstanceSize是對齊方式,根據(jù)代碼可以看到64位下是8字節(jié)對齊,32位下是4字節(jié)對齊,計(jì)算方式是對WORD_MASK取反,然后進(jìn)行與的操作。所以這個(gè)方法計(jì)算了需要開辟的空間大小。
我們回到方法_class_createInstanceFromZone中,zone這個(gè)值傳進(jìn)來的是nil,所以下面走的方法是 obj = (id)calloc(1, size);
而實(shí)際去開空間的操作是在calloc,它的具體實(shí)現(xiàn)在libmalloc中,它開辟了內(nèi)存空間,并且返回了一個(gè)指向該內(nèi)存地址的指針。
4.3 initInstanceIsa 將類和isa指針關(guān)聯(lián)
上面的instanceSize和calloc完成了空間的計(jì)算和開辟,接下來執(zhí)行來到
if(!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
zone是nil,fast是是否可以創(chuàng)建Nonpointer的返回。會(huì)來到方法initInstanceIsa。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
主要調(diào)用方法initIsa。
- initIsa
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
isa_t newisa(0);
// nonpointer:ture
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
newisa.bits = ISA_MAGIC_VALUE;
newisa.setClass(cls, **this**);
newisa.extra_rc = 1;
}
isa() = newisa;
}
從方法initInstanceIsa傳進(jìn)來的第二個(gè)參數(shù)nonpointer是ture,所以走流程 else。
SUPPORT_INDEXED_ISA不同的架構(gòu)下處理不同。64位下的直接走else的部分。走setClass。該方法關(guān)鍵代碼:
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
shiftcls = (uintptr_t)newCls >> 3;
}
該方法其實(shí)就是初始化一個(gè)isa_t然后綁定指向給cls。至于右移3位,因?yàn)槭前俗止?jié)對齊,指針后三位是無用的0,0與上任何都是0,估計(jì)是為了省內(nèi)存減少消耗吧。
5. alloc的流程圖

6. init 和 new
看過了上面alloc的流程,也簡單看一下我們常用的init還有new。
6.1 init
進(jìn)行斷點(diǎn)調(diào)試,我們找到方法_objc_rootInit

它的實(shí)現(xiàn):
id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
嗯,傳進(jìn)來啥,返回啥??。
6.2 new
同樣的,new的實(shí)際實(shí)現(xiàn)方法是objc_opt_new。它的實(shí)現(xiàn):
id
objc_opt_new(Class cls)
{
if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
return [callAlloc(cls, false/checkNil/) init];
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}
關(guān)鍵代碼是[callAlloc(cls, false/checkNil/) init],實(shí)際上也是調(diào)用的 [alloc init]。
7.總結(jié)
alloc的主要流程:
- 類沒有被加載進(jìn)來時(shí),也就是沒有實(shí)現(xiàn)allocWithZone方法時(shí),那么再發(fā)送一遍alloc消息,直到已經(jīng)allocWithZone了,確保已經(jīng)被加載進(jìn)來。
- 通過instanceSize計(jì)算需要的空間。
- calloc開辟實(shí)際的空間。
- initInstanceIsa將class與isa進(jìn)行關(guān)聯(lián)。
- init 本質(zhì)返回傳入的self對象
- new 等價(jià)于alloc+init
關(guān)于(uintptr_t)newCls >> 3,到底為啥還是有點(diǎn)模糊,只是猜測是節(jié)約內(nèi)存,后面有新的發(fā)現(xiàn)再補(bǔ)充~