引言
當(dāng)我們創(chuàng)建一個(gè)對(duì)象的時(shí)候 比如: [[Person alloc] init] alloc 到底做了什么 init到底做了什么 ? 這就是這篇文檔要給大家分享的一些個(gè)人見(jiàn)解,希望大家多提意見(jiàn),共同學(xué)習(xí).
探索路程
如下圖 直接上代碼

上述代碼說(shuō)明p1,p2,p3指向了相同的內(nèi)存地址,init并沒(méi)有創(chuàng)建空間 真正申請(qǐng)空間的是alloc申請(qǐng)了空間.
%p -> &p1:是對(duì)象的指針地址,
%p -> p1: 是對(duì)象指針指向的的內(nèi)存地址
由p1,p2,p3的指針地址推斷,棧內(nèi)存是連續(xù)的(0x30 + 0x08 = 0x38, 0x28 + 0x08 = 0x30 [這里是16進(jìn)制運(yùn)算]),并且所占內(nèi)存都是8字節(jié)(指針占內(nèi)存8字節(jié))

源碼探索
要想知道alloc到底做了什么事我們需要知道他的源碼里面到底怎么實(shí)現(xiàn)的,由于蘋果API是點(diǎn)不進(jìn)去的 我們只能根據(jù)符號(hào)斷點(diǎn)或者其他方法去探索 我們可以從網(wǎng)上下載他的源碼去分析.
蘋果開(kāi)源庫(kù):
1:https://opensource.apple.com/source 所以開(kāi)源庫(kù)都在里面,包括各個(gè)老版本
2:https://opensource.apple.com 可以根據(jù)系統(tǒng)版本選擇更新的下載
3 : 編譯源碼,可參考https://github.com/LGCooci/objc4_debug

知道哪里下載庫(kù)了,那我們?cè)趺粗繿lloc屬于哪個(gè)庫(kù)呢?
這里為大家提供3個(gè)方法,供參考
方法一: 下符號(hào)斷點(diǎn)的形式直接跟流程
下符號(hào)斷點(diǎn)的方法如圖

方法二:Ctrl + step into

按住Ctrl,多點(diǎn)幾次下一步,來(lái)到了 objc_alloc

把objc_alloc作為符號(hào)斷點(diǎn),加上的一瞬間就又找到了libobjc.A.dylib

方法三:匯編查看跟流程(最常用的)
Debug -> Debug Workflow -> Always Show Disassembly 進(jìn)入?yún)R編

得到 objc_alloc

用方法二下符號(hào)斷點(diǎn)又可以拿到libobjc.A.dylib
找到源碼庫(kù)libobjc.A.dylib 即 objc4
下載源碼+編譯源碼請(qǐng)移步到 iOS_objc4-781.2 最新源碼編譯調(diào)試
打開(kāi)源碼工程,開(kāi)始alloc源碼探索
通過(guò) command + 鼠標(biāo)左鍵 一步一步進(jìn)入源碼看流程
如下圖 NSObject 和它的子類是有點(diǎn)區(qū)別的 NSObject 沒(méi)走alloc只走了一次callAlloc是因?yàn)橄到y(tǒng)幫我們做了(alloc底層會(huì)被hook去調(diào)用objc_alloc,所以先走objc_alloc再走alloc) MyNSObject走了兩次callAlloc 第二次才是真正的去開(kāi)辟空間.


所以可以看到在調(diào)用NSObject *objc = [NSObject alloc];時(shí),其實(shí)調(diào)用的是objc中的objc_alloc方法。
二、OC 的 alloc 深入探究
經(jīng)過(guò)上面一步,我們就能淺嘗輒止嗎?當(dāng)然不能;所以我搞來(lái)一份objc4源碼繼續(xù)我們的探究。
看到源碼 NSObject.mm 文件,里面的alloc方法

他會(huì)調(diào)用_objc_rootAlloc

- 問(wèn)題:
我上面看到 [NSObject alloc]看到的不是會(huì)到objc_alloc來(lái)嗎?怎么源碼分析的到了_objc_rootAlloc? - 答:我們知道OC代碼有運(yùn)行時(shí)的特點(diǎn),即方法的調(diào)用不是在編譯時(shí)確定的,而是運(yùn)行時(shí),不懂的補(bǔ)一下編譯原理。我們?cè)趏bjc運(yùn)行時(shí)源碼中查找objc_alloc,發(fā)現(xiàn)如下的代碼
static void
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) {
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == @selector(retain)) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == @selector(release)) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == @selector(autorelease)) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
發(fā)現(xiàn)消息發(fā)送時(shí),當(dāng)msg->imp == &objc_msgSend_fixup 進(jìn)行了方法實(shí)現(xiàn)(IMP)的替換。
什么時(shí)候
msg->imp == &objc_msgSend_fixup成立還沒(méi)有確定的探究,后續(xù)了解會(huì)進(jìn)一步更新。從源碼的運(yùn)行看[NSObject alloc]會(huì)走到objc_alloc;但是其子類的[LGPerson alloc]會(huì)調(diào)用_objc_rootAlloc
不管是_objc_rootAlloc或者objc_alloc從圖4看都會(huì)進(jìn)入callAlloc這個(gè)函數(shù),不過(guò)最后兩個(gè)參數(shù)不同;我們姑且往下走,進(jìn)入callAlloc去看看究竟。代碼如下

- 這里看到有兩個(gè)宏定義函數(shù):
slowpath,fastpath. 這兩個(gè)函數(shù)是用于編譯器優(yōu)化;slowpath低概率會(huì)走;fastpath大概率會(huì)走,如果項(xiàng)目中有需求,自己也可以用上,讓自己的代碼質(zhì)量提高。 -
hasCustomAWZ是否有自定義的AWZ;經(jīng)過(guò)運(yùn)行實(shí)際得出,當(dāng)類的第一次運(yùn)行時(shí)slowpath,fastpath判斷都為false,后面在運(yùn)行時(shí)fastpath為true;不過(guò)我們看到下面的函數(shù)又是消息的發(fā)送,調(diào)用的是alloc和allocWithZone,其實(shí)接著往下走,可以發(fā)現(xiàn),他又回到了callAlloc,并且此時(shí)的fastpath為true;
那么函數(shù)此時(shí)應(yīng)來(lái)到_objc_rootAllocWithZone
unsigned
class_createInstances(Class cls, size_t extraBytes,
id *results, unsigned num_requested)
{
return _class_createInstancesFromZone(cls, extraBytes, nil,
results, num_requested);
}
那么我們?nèi)タ纯?code>_class_createInstancesFromZone
unsigned
_class_createInstancesFromZone(Class cls, size_t extraBytes, void *zone,
id *results, unsigned num_requested)
{
unsigned num_allocated;
if (!cls) return 0;
size_t size = cls->instanceSize(extraBytes);
num_allocated =
malloc_zone_batch_malloc((malloc_zone_t *)(zone ? zone : malloc_default_zone()),
size, (void**)results, num_requested);
for (unsigned i = 0; i < num_allocated; i++) {
bzero(results[i], size);
}
// Construct each object, and delete any that fail construction.
unsigned shift = 0;
bool ctor = cls->hasCxxCtor();
for (unsigned i = 0; i < num_allocated; i++) {
id obj = results[i];
obj->initIsa(cls); // fixme allow nonpointer
if (ctor) {
obj = object_cxxConstructFromClass(obj, cls,
OBJECT_CONSTRUCT_FREE_ONFAILURE);
}
if (obj) {
results[i-shift] = obj;
} else {
shift++;
}
}
return num_allocated - shift;
}
這里的邏輯大體分為
- 1、求出實(shí)例所需內(nèi)存大小
size_t size = cls->instanceSize(extraBytes);
- 2、分配空間
num_allocated =
malloc_zone_batch_malloc((malloc_zone_t *)(zone ? zone : malloc_default_zone()),
size, (void**)results, num_requested);
3、綁定isa到類
obj->initIsa(cls); // fixme allow nonpointer
接下來(lái)返回對(duì)應(yīng)類的首地址
內(nèi)存分配分析
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:根據(jù)字節(jié)對(duì)齊機(jī)制計(jì)算出相關(guān)的分配所需內(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 : 根據(jù)第一步計(jì)算所需空間,向系統(tǒng)開(kāi)辟內(nèi)存
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
3:根據(jù)分配的內(nèi)存情況管理對(duì)象
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);
}
我們順著代碼進(jìn)入到第一步
-
步驟1:計(jì)算相關(guān)內(nèi)存大小
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ù)斷點(diǎn)我們能進(jìn)入到 return cache.fastInstanceSize(extraBytes);這里,再次進(jìn)入代碼到
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;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
最后通過(guò)一個(gè)計(jì)算得到是一個(gè)align16的數(shù)據(jù)返回類型,第一段代碼告訴我們,系統(tǒng)是通過(guò)16字節(jié)對(duì)齊的方式,如果分配小于16字節(jié),就直接等于16字節(jié)。所以此時(shí)的size 是16,
align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
size : 16字節(jié)對(duì)齊,此時(shí)是16,
extra :是系統(tǒng)計(jì)算的額外信息,根據(jù)斷點(diǎn)此時(shí)是0,
-
FAST_CACHE_ALLOC_DELTA16:是宏定義 :此時(shí)是8
image
所以結(jié)合最終返回的align16的值是 align16(8)
我們?cè)诖诉M(jìn)入align16的定義函數(shù)
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
此處代碼的意思就是,上一步計(jì)算結(jié)果 的值加上15,和15的值取反過(guò)后再進(jìn)行 按位與操作,
什么意思呢:例如上一步我們計(jì)算的結(jié)果是8,再加上15 結(jié)果等于23,23在內(nèi)存中的標(biāo)識(shí)是這樣的
0000 0000 0001 0111
15的內(nèi)存表示
0000 0000 0000 1111
15取反操作的值是
1111 1111 1111 0000
所以按位與的操作就是 23 & ~15
0000 0000 0001 0111
1111 1111 1111 0000
0000 0000 0001 0000(結(jié)果)十進(jìn)制的16
如果是內(nèi)存分配大于16 的,比如 20 加上15表示成 35,內(nèi)存表示(0000 0000 0010 0011)
按位與操作是 35 & ~15
0000 0000 0010 0011
1111 1111 1111 0000
0000 0000 0010 0000(結(jié)果) 十進(jìn)制的32,
所以這就是16字節(jié)對(duì)齊的核心機(jī)制,相信我已經(jīng)介紹的夠清晰了,這就是iOS內(nèi)存分配的最新機(jī)制。
-
步驟2 :分配內(nèi)存
根據(jù)計(jì)算的內(nèi)存size_t size 進(jìn)行內(nèi)存的分配,

-
步驟3 關(guān)聯(lián)對(duì)象類型
通過(guò)InitInstanceIsa 方法進(jìn)行關(guān)聯(lián)類對(duì)象實(shí)例,
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
總結(jié)
這就是 alloc 的完整流程;
通過(guò)ISA的關(guān)聯(lián),我們就完成了一個(gè)對(duì)象的內(nèi)存分配情況,也就是一個(gè)對(duì)象的alloc 的完整流程,因?yàn)槿魏我粋€(gè)類對(duì)象都可以通過(guò)ISA指針找到相應(yīng)的父類,以及元類,這樣對(duì)象的各種初始化實(shí)例方法都能通過(guò)ISA指針指向找到相應(yīng)的IMP。從而調(diào)用方法進(jìn)行消息轉(zhuǎn)發(fā)。

這就是alloc的大致流程
init 源碼探索
alloc源碼探索完了,接下來(lái)探索init源碼,通過(guò)源碼可知,inti的源碼實(shí)現(xiàn)有以下兩種
類方法 init
+ (id)init {
return (id)self;
}
這里的init是一個(gè)構(gòu)造方法 ,是通過(guò)工廠設(shè)計(jì)(工廠方法模式),主要是用于給用戶提供構(gòu)造方法入口。這里能使用id強(qiáng)轉(zhuǎn)的原因,主要還是因?yàn)?內(nèi)存字節(jié)對(duì)齊后,可以使用類型強(qiáng)轉(zhuǎn)為你所需的類型
實(shí)例方法 init
- 通過(guò)以下代碼進(jìn)行探索實(shí)例方法 init
LGPerson *objc = [[LGPerson alloc] init];
- 通過(guò)
main中的init跳轉(zhuǎn)至init的源碼實(shí)現(xiàn)
- (id)init {
return _objc_rootInit(self);
}
- 跳轉(zhuǎn)至
_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;
}
有上述代碼可以,返回的是傳入的self本身。
new 源碼探索
一般在開(kāi)發(fā)中,初始化除了init,還可以使用new,兩者本質(zhì)上并沒(méi)有什么區(qū)別,以下是objc中new的源碼實(shí)現(xiàn),通過(guò)源碼可以得知,new函數(shù)中直接調(diào)用了callAlloc函數(shù)(即alloc中分析的函數(shù)),且調(diào)用了init函數(shù),所以可以得出new 其實(shí)就等價(jià)于 [alloc init]的結(jié)論
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
但是一般開(kāi)發(fā)中并不建議使用new,主要是因?yàn)橛袝r(shí)會(huì)重寫init方法做一些自定義的操作,例如 initWithXXX,會(huì)在這個(gè)方法中調(diào)用[super init],用new初始化可能會(huì)無(wú)法走到自定義的initWithXXX部分。
例如,在CJLPerson中有兩個(gè)初始化方法,一個(gè)是重寫的父類的init,一個(gè)是自定義的initWithXXX方法,如下圖所示

-
使用 alloc + init 初始化時(shí),打印的情況如下
image -
使用new 初始化時(shí),打印的情況如下
image
總結(jié)
- 如果子類沒(méi)有重寫父類的init,new會(huì)調(diào)用父類的init方法
- 如果子類重寫了父類的init,new會(huì)調(diào)用子類重寫的init方法
- 如果使用 alloc + 自定義的init,可以幫助我們自定義初始化操作,例如傳入一些子類所需參數(shù)等,最終也會(huì)走到父類的init,相比new而言,擴(kuò)展性更好,更靈活。
謝謝你的閱讀!
鏈接:http://m.itdecent.cn/p/b72018e88a97
推薦參考:http://m.itdecent.cn/p/dbba973c7446


