首先我們先看下alloc
創(chuàng)建對(duì)象的一個(gè)整體流程圖:
1. 如何分析 alloc
方法的執(zhí)行流程
1.1 通過符號(hào)斷點(diǎn)分析
首先我們?cè)赼lloc方法調(diào)用的地方打上斷點(diǎn)纱昧,待程序運(yùn)行到改行時(shí),按住 ctrl
鍵,同時(shí)數(shù)遍點(diǎn)擊 xcode
底部的 Debug
窗口的 step into
即可進(jìn)入。具體方法如下圖所示:
注意:一定要在程序運(yùn)行到
alloc
方法的時(shí)候读拆,在enable
符號(hào)斷點(diǎn)供汛,否者程序運(yùn)行時(shí)創(chuàng)建的類,會(huì)反復(fù)hit
這個(gè)符號(hào)斷點(diǎn)
1.2 直接閱讀匯編代碼
通過設(shè)置Always Show Disassembly
閱讀程序的匯編代碼悉尾,分析alloc
的執(zhí)行流程。具體方法: 點(diǎn)擊xcode
頂部的Debug
菜單挫酿,選擇Debug Workflow
,勾選Always Show Disassembly
构眯。打上斷點(diǎn),重新運(yùn)行程序早龟,將直接進(jìn)入?yún)R編代碼窗口鸵赖。此方法可結(jié)合符號(hào)斷點(diǎn),快速定位到相關(guān)的方法拄衰。
1.3 通過編譯源碼
此方法它褪,相較于閱讀匯編代碼,對(duì)于我個(gè)人而言比較簡(jiǎn)單翘悉,也比較直觀茫打,但源碼的編譯過程比較繁瑣,問題比較多妖混,需要很多耐心去解決相關(guān)的問題老赤。想走捷徑的同學(xué)可以去這里下載相關(guān)的代碼:Github。如果想嘗試下自己編輯源代碼制市,可以去這里下載objc4
源碼抬旺,opensource
可能會(huì)遇到的問題:
1.3.1 設(shè)置了斷點(diǎn)卻無法觸發(fā)
選擇 Target
-> Build Phases
-> Compile Sources
將 main.m
文件移動(dòng)到最前面。
1.3.1 斷點(diǎn)生效了,但是無法進(jìn)入alloc方法的實(shí)現(xiàn)
選擇 Target
-> Build Setting
-> Enable Hardened Runtime
將其設(shè)置為Yes
2. alloc
方法分析
首先會(huì)調(diào)用:
+ (id)alloc {
return _objc_rootAlloc(self);
}
再次進(jìn)入會(huì)調(diào)用:
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
再次進(jìn)入會(huì)調(diào)用:
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
// 這里的if判斷的是當(dāng)前類或者父類有沒有實(shí)現(xiàn)alloc/allocWithZone方法
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));
}
點(diǎn)擊_objc_rootAllocWithZone
會(huì)進(jìn)入
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);
}
再次點(diǎn)擊_class_createInstanceFromZone
就進(jìn)入了真在的核心方法祥楣。
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); // 計(jì)算出需要的內(nèi)存空間的大小
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);// 開辟內(nèi)存空間
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);// isa指針跟cls關(guān)聯(lián)起來
} 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);
}
三個(gè)核心的方法:
instanceSize
: 先計(jì)算出需要的內(nèi)存空間大小
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.
if (size < 16) size = 16;
return size;
}
// 快速計(jì)算
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// 字節(jié)對(duì)齊
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
// 計(jì)算類中變量所需的大小
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
// 最后調(diào)用 align16 方法开财,進(jìn)行字節(jié)對(duì)齊汉柒,返回值必然是16的倍數(shù)
// 這個(gè)方法寫的很巧妙、優(yōu)雅责鳍,值得學(xué)習(xí)碾褂,深究
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
calloc
: 向系統(tǒng)申請(qǐng)開辟內(nèi)存,返回地址指針
由instanceSize
計(jì)算出所需的內(nèi)存空間大小后,交由系統(tǒng)去開辟相應(yīng)的內(nèi)存空間大小历葛。首先我們通過斷點(diǎn)可以看出正塌,obj
在創(chuàng)建的時(shí)候就分配一塊臟地址。
經(jīng)過調(diào)用calloc
方法以后恤溶,開辟相應(yīng)的內(nèi)存空間乓诽,但這時(shí)候還并未與相應(yīng)的類關(guān)聯(lián)起來
initInstanceIsa
: 關(guān)聯(lián)到相應(yīng)的類
通過斷點(diǎn)的可以看到,initInstanceIsa
調(diào)用以后咒程,已經(jīng)將isa
指針與相應(yīng)的類關(guān)聯(lián)起來鸠天。最終返回obj
,至此alloc
的流程基本調(diào)用完畢。
3. 總結(jié)
通過以上的分析孵坚,我們可以得出調(diào)用alloc
方法以后,會(huì)開辟出相應(yīng)大小的內(nèi)存空間窥淆。而調(diào)用init
方法卖宠,會(huì)將相應(yīng)的地址指向該內(nèi)存空間。代碼驗(yàn)證:
Person *p1 = [Person alloc];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
NSLog(@"%@-%p", p1, &p1); // <Person: 0x10103f560>-0x7ffeefbff440
NSLog(@"%@-%p", p2, &p2); // <Person: 0x10103f560>-0x7ffeefbff428
NSLog(@"%@-%p", p3, &p3); // <Person: 0x10103f560>-0x7ffeefbff430
Person *p4 = [Person alloc];
NSLog(@"%@-%p", p4, &p4); // <Person: 0x1014457b0>-0x7ffeefbff438