在源碼分析前,我們先來看看如下的代碼:
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [p1 init];
LGPerson *p3 = [p1 init];
LGNSLog(@"對象內容:%@ - 內存地址:%p - 指針地址:%p",p1,p1,&p1);
LGNSLog(@"對象內容:%@ - 內存地址:%p - 指針地址:%p",p2,p2,&p2);
LGNSLog(@"對象內容:%@ - 內存地址:%p - 指針地址:%p",p3,p3,&p3);
運行輸出:
對象內容:<LGPerson: 0x6000038b0260> - 內存地址:0x6000038b0260 - 指針地址:0x7ffee4ddb058
對象內容:<LGPerson: 0x6000038b0260> - 內存地址:0x6000038b0260 - 指針地址:0x7ffee4ddb050
對象內容:<LGPerson: 0x6000038b0260> - 內存地址:0x6000038b0260 - 指針地址:0x7ffee4ddb048
通過上面的運行結果可以得出結論:
3個對象指向同一個內存空間,所以內容和內存地址是相同的,但是對象的指針地址是不同的。
那么我們就要思考了,在初始化過程中,alloc做了什么?init做了什么?
- 下載源碼,編譯源碼,可參考http://www.lxweimin.com/p/e74299043f23
alloc源碼探索
alloc + init 整體源碼的探索流程如下:- 【第一步】首先根據main函數中的LGPerson類的alloc方法進入alloc方法的源碼實現(即源碼分析開始)
+ (id)alloc {
return _objc_rootAlloc(self);
}
- 【第二步】跳轉至_objc_rootAlloc的源碼實現
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
- 【第三步】跳轉至callAlloc的源碼實現
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));
}
如上所示,在calloc方法中,當我們無法確定實現走到哪部時,可以通過斷點調試,判斷執(zhí)行走哪部分邏輯。這里是執(zhí)行到_objc_rootAllocWithZone
slowpath & fastpath
其中關于slowpath和fastpath這里需要簡要說明下,這兩個都是objc源碼中定義的宏,其定義如下:
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
其中的__builtin_expect指令是由gcc引入的,
1、目的:編譯器可以對代碼進行優(yōu)化,以減少指令跳轉帶來的性能下降。即性能優(yōu)化
2、作用:允許程序員將最有可能執(zhí)行的分支告訴編譯器。
3、指令的寫法為:__builtin_expect(EXP, N)。表示 EXP==N的概率很大。
4、fastpath定義中__builtin_expect((x),1)表示 x 的值為真的可能性更大;即 執(zhí)行if 里面語句的機會更大
5、slowpath定義中的__builtin_expect((x),0)表示 x 的值為假的可能性更大。即執(zhí)行 else 里面語句的機會更大
6、在日常的開發(fā)中,也可以通過設置來優(yōu)化編譯器,達到性能優(yōu)化的目的,設置的路徑為:Build Setting --> Optimization Level --> Debug --> 將None 改為 fastest 或者 smallest
cls->ISA()->hasCustomAWZ()
其中fastpath中的 cls->ISA()->hasCustomAWZ() 表示判斷一個類是否有自定義的 +allocWithZone 實現,這里通過斷點調試,是沒有自定義的實現,所以會執(zhí)行到 if 里面的代碼,即走到_objc_rootAllocWithZone
- 【第四步】跳轉至_objc_rootAllocWithZone
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
- 【第五步】跳轉至_class_createInstanceFromZone的源碼實現,這部分是alloc源碼的核心操作,由下面的流程圖及源碼可知,該方法的實現主要分為三部分
cls->instanceSize:計算需要開辟的內存空間大小
calloc:申請內存,返回地址指針
obj->initInstanceIsa:將 類 與 isa 關聯
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)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
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);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
根據源碼分析,得出其實現流程如下所示:cls->instanceSize:計算需要開辟的內存空間大小
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.
if (size < 16) size = 16;
return size;
}
align16源碼:
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
流程如下calloc:申請內存,返回地址指針
通過instanceSize計算的內存大小,向內存中申請大小為size的內存,并賦值給obj,因此obj是指向內存地址的指針
obj = (id)calloc(1, size);
這里我們可以通過斷點來印證上述的說法,在未執(zhí)行calloc時,po obj為nil,執(zhí)行后,再po obj法線,返回了一個16進制的地址在平常的開發(fā)中,一般一個對象的打印的格式都是類似于這樣的<LGPerson: 0x01111111f>(是一個指針)。為什么這里不是呢?
- 主要是因為objc 地址 還沒有與傳入 的 cls進行關聯,
- 同時印證了 alloc的根本作用就是 開辟內存
obj->initInstanceIsa:類與isa關聯
經過calloc可知,內存已經申請好了,類也已經傳入進來了,接下來就需要將 類與 地址指針 即isa指針進行關聯,其關聯的流程圖如下所示主要過程就是初始化一個isa指針,并將isa指針指向申請的內存地址,在將指針與cls類進行關聯
同樣也可以通過斷點調試來印證上面的說法,在執(zhí)行完initInstanceIsa后,在通過po obj可以得出一個對象指針總結:
- 通過對alloc源碼的分析,可以得知alloc的主要目的就是開辟內存,而且開辟的內存需要使用16字節(jié)對齊算法,現在開辟的內存的大小基本上都是16的整數倍
- 開辟內存的核心步驟有3步:計算 -- 申請 -- 關聯
init源碼探索
類方法 init:
+ (id)init {
return (id)self;
}
這里的init是一個構造方法 ,是通過工廠設計(工廠方法模式),主要是用于給用戶提供構造方法入口。這里能使用id強轉的原因,主要還是因為內存字節(jié)對齊后,可以使用類型強轉為你所需的類型
實例方法 init:
通過以下代碼進行探索實例方法 init
LGPerson *objc = [[LGPerson alloc] init];
通過main中的init跳轉至init的源碼實現:
- (id)init {
return _objc_rootInit(self);
}
--------------
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;
}
有上述代碼可以證明,返回的是傳入的self本身。
new源碼探索
一般在開發(fā)中,初始化除了init,還可以使用new,兩者本質上并沒有什么區(qū)別,以下是objc中new的源碼實現,通過源碼可以得知,new函數中直接調用了callAlloc函數(即alloc中分析的函數),且調用了init函數,所以可以得出new 其實就等價于 [alloc init]的結論
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}