在oc底層原理一里我們探索了如何定位底層源碼的三種方式,在oc底層原理二里我們配置了objc4-781 的可編譯環(huán)境,讓我們更直觀的探索底層流程。
可直接下載編譯成功的objc4-781
本文就在以上兩篇的基礎(chǔ)上,探究下alloc的實(shí)現(xiàn)流程。
首先打開(kāi)配置好的objc,利用符號(hào)斷點(diǎn)的方式走一下alloc的整個(gè)流程,流程圖如下:

一步步走下來(lái),我們會(huì)發(fā)現(xiàn)
_class_createInstanceFromZone方法是alloc流程的核心實(shí)現(xiàn):
_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
//讀取類(lèi)的信息
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
//詢問(wèn)需要開(kāi)辟的內(nèi)存大小,extraBytes為0
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;
}
//將類(lèi)和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);
}
_class_createInstanceFromZone方法創(chuàng)建實(shí)例內(nèi)存空間,主要有三部分實(shí)現(xiàn):
- cls->instanceSize:計(jì)算需要開(kāi)辟的內(nèi)存空間大小
- calloc:申請(qǐng)內(nèi)存,返回地址指針
- obj->initInstanceIsa:將類(lèi)與isa關(guān)聯(lián)
alloc核心方法
instanceSize源碼
size_t instanceSize(size_t extraBytes) const {
//快速計(jì)算內(nèi)存大小
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
//計(jì)算isa和成員變量需要的內(nèi)存大小
size_t size = alignedInstanceSize() + extraBytes;
//如果不足16字節(jié)的,補(bǔ)齊16字節(jié)
if (size < 16) size = 16;
return size;
}
首先就是有個(gè)fastpath的判斷,判斷是否需要編譯器優(yōu)化。根據(jù)調(diào)試可以從緩存中讀取所需開(kāi)辟空間大小cache.fastInstanceSize。
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
//判斷extra是否在編譯時(shí)就可以確定其為常量,如果extra為常量,該函數(shù)返回1,否則返回0。
//如果extra為常量,可以在代碼中做一些優(yōu)化來(lái)減少處理extra的復(fù)雜度。
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
//十六字節(jié)對(duì)齊
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
builtin函數(shù)是GCC提供的,可以實(shí)現(xiàn)一些簡(jiǎn)單快捷的功能來(lái)方便程序編寫(xiě),另外,很多builtin函數(shù)可用來(lái)優(yōu)化編譯結(jié)果。
__builtin_constant_p (extra):判斷extra是否在編譯時(shí)就可以確定其為常量,如果extra為常量,該函數(shù)返回1,否則返回0。如果extra為常量,可以在代碼中做一些優(yōu)化來(lái)減少處理extra的復(fù)雜度。
align16(size + extra - FAST_CACHE_ALLOC_DELTA16)通過(guò)字面就可以理解16字節(jié)對(duì)齊,看下源碼:
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
size_t獲取sizeof計(jì)算的數(shù)據(jù)類(lèi)型,保存的一般是個(gè)整數(shù)。
我們以align(8)為例看下16字節(jié)對(duì)齊算法:
實(shí)際算法為(8 + size_t(15)) & ~size_t(15),即(8+15)&~15,即23&~15,二進(jìn)制表示0x 0001 0111 & 0x 1111 0000,與運(yùn)算相同的為1,不同的為0,所以我們知道這其實(shí)就是對(duì)16位以下清零的操作,最終計(jì)算為0x 0001 0000,即16。
為什么要16字節(jié)對(duì)齊呢?
我們都知道內(nèi)存是由一個(gè)個(gè)字節(jié)組成的,但cpu在讀取的時(shí)候不會(huì)按照字節(jié)去讀取,cpu把內(nèi)存當(dāng)成是一塊一塊的,塊的大小可以是2,4,8,16 個(gè)字節(jié),因此CPU在讀取內(nèi)存的時(shí)候是一塊一塊進(jìn)行讀取的,塊的大小稱(chēng)為內(nèi)存讀取粒度。
假設(shè)CPU要讀取一個(gè)8字節(jié)大小的數(shù)據(jù)到寄存器中,如果數(shù)據(jù)從0字節(jié)開(kāi)始,直接將0-7四個(gè)字節(jié)完全讀取到寄存器。但如果數(shù)據(jù)從1字節(jié)開(kāi)始讀取,首先先將0-7字節(jié)讀到寄存器,并再次讀取8-15字節(jié)的數(shù)據(jù)進(jìn)寄存器,接著把0字節(jié),9-15字節(jié)的數(shù)據(jù)剔除,最后合并1-8字節(jié)的數(shù)據(jù)進(jìn)寄存器,做了這么多額外操作,大大降低了CPU的性能。
而對(duì)齊后則以空間換時(shí)間,大大提高了cpu的讀取速度。
而我們知道每個(gè)對(duì)象都有個(gè)isa指針,isa指針占用8個(gè)字節(jié),每一個(gè)屬性也是占用8個(gè)字節(jié)。當(dāng)無(wú)屬性的時(shí)候每個(gè)對(duì)象最少占8個(gè)字節(jié)。如果用8字節(jié)對(duì)齊的話,對(duì)象和對(duì)象之間的isa就會(huì)連在一塊,可能訪問(wèn)時(shí)就會(huì)出現(xiàn)問(wèn)題。所以16字節(jié)對(duì)齊一方面解決了這樣的隱患,而且也方便了以后的擴(kuò)展。
calloc源碼
void *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);
calloc在內(nèi)存的動(dòng)態(tài)存儲(chǔ)區(qū)中分配count個(gè)長(zhǎng)度為size的連續(xù)空間。num:屬性個(gè)數(shù)+isa,size:instanceSize計(jì)算出的size。
分配好內(nèi)存后,返回地址指針。
initIsa源碼
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
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
obj->initInstanceIsa初始化一個(gè)isa指針,指向這個(gè)對(duì)象,然后綁定這個(gè)對(duì)象。具體的綁定過(guò)程后面抽一篇具體研究。