前言
想必大家做開發(fā)的時候敲過無數(shù)遍 [[XXX alloc] init],那么alloc函數(shù)到底做了什么工作知道嗎,不就是對象初始化開辟內(nèi)存嘛,那你能詳細(xì)點(diǎn)描述具體流程嗎?這時候就一臉懵逼,what?今天筆者就帶大家來探索下alloc函數(shù)具體做了什么。
大家先來看下面代碼打印的日志,分別打印了p1p2p3三個指針指向的內(nèi)容,指向的內(nèi)存地址,指針地址。

圖1
我們發(fā)現(xiàn)這三個指針指向的內(nèi)容和內(nèi)存地址是一樣的,由p1p2可以看出alloc后對象就已經(jīng)創(chuàng)建一個對象并且分配好內(nèi)存了,接下來init函數(shù)似乎只是初始化一個指針來指向alloc創(chuàng)建的對象。那么我們接下來就通過源碼去探索alloc/init做了什么。
準(zhǔn)備
- 在蘋果的opensourse下載objc4-781源碼
- 編譯源碼,如果有啥問題推薦下好用的博客objc4-781 源碼編譯 & 調(diào)試
開始追蹤objc源碼查看alloc具體操作
-
第一步 點(diǎn)擊main函數(shù)里面LGperson類的alloc方法跳轉(zhuǎn)進(jìn)入圖2所示的alloc實(shí)現(xiàn)
圖2 - 第二步 繼續(xù)點(diǎn)擊
return _objc_rootAlloc(self);跳轉(zhuǎn)下去到達(dá)_objc_rootAlloc的實(shí)現(xiàn)方法
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
- 第三步 繼續(xù)點(diǎn)擊跳轉(zhuǎn)至
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)源碼實(shí)現(xiàn)
// 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__ //objc有兩個版本 ,OBJC2為編譯優(yōu)化版本
if (slowpath(checkNil && !cls)) return nil;
//判斷這個類是否有自定義的 +allocWithZone 實(shí)現(xiàn),沒有則走到if里面的實(shí)現(xiàn)
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)調(diào)試發(fā)現(xiàn)走到了if __OBJC2__里面,調(diào)用的_objc_rootAllocWithZone
- 第四步 然后我們就繼續(xù)點(diǎn)擊
_objc_rootAllocWithZone函數(shù)跳轉(zhuǎn)發(fā)現(xià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);
}
- 第五步 我們繼續(xù)點(diǎn)擊
_class_createInstanceFromZone跳轉(zhuǎn),然后終于來到了alloc核心實(shí)現(xià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;
// 1:要開辟多少內(nèi)存
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 2;怎么去申請內(nèi)存
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
// 3: 將 cls類 與 obj指針(即isa) 關(guān)聯(lián)
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);
}
我們發(fā)現(xiàn)alloc做了三件事
-
cls->instanceSize(extraBytes);計算需要開辟的內(nèi)存空間大小。 -
calloc根據(jù)size申請內(nèi)存,然后返回該內(nèi)存地址的指針。 -
obj->initInstanceIsa將 cls類 與 obj指針(即isa) 關(guān)聯(lián)。
接下來我們看看init做了什么
- 第一步 點(diǎn)擊LGPerson的init函數(shù)跳轉(zhuǎn),發(fā)現(xiàn)調(diào)用了
_objc_rootInit。
init實(shí)現(xiàn) - 第二步 然后我們繼續(xù)點(diǎn)擊
_objc_rootInit函數(shù)跳轉(zhuǎn),我們發(fā)現(xiàn)驚人的事實(shí),它竟然什么都沒做!直接return了傳進(jìn)來的obj對象self。
_objc_rootInit函數(shù)實(shí)現(xiàn)
那么為什么init里面啥都不做還要封裝一層呢?這不是廢操作嘛,當(dāng)然不是,這是蘋果遵循工廠模式給我們開發(fā)者封裝的,比如我們初始化對象的時候有些屬性我們得傳進(jìn)來賦值,那么init此時就有作用了,我們可以重寫init函數(shù)對對象做些操作,類似于下面
- (id)init {
if (self = [super init]) {
self.name = @"張三";
}
return self;
}
所以整個alloc/init大致流程如下

alloc/init流程
總結(jié)
一句話概括就是alloc做了三件事1. cls->instanceSize(extraBytes);計算對象需要多大內(nèi)存空間。2.調(diào)用calloc函數(shù)申請內(nèi)存并返回內(nèi)存的指針地址。3.obj->initInstanceIsa 將 cls類 與 obj指針(即isa) 關(guān)聯(lián)。 init做了一件事就是返回當(dāng)前對象。


