一、alloc實(shí)現(xiàn)流程


Person *p = [Person alloc];
在Foundation層我們初始化一個(gè)對(duì)象需要調(diào)用alloc方法,通過查看堆棧信息我們可以大致知道函數(shù)調(diào)用過程如下:
[圖片上傳失敗...(image-488e4f-1599590590512)]
1. alloc函數(shù)調(diào)用 _objc_rootAlloc 函數(shù),傳入相應(yīng)的類,并且調(diào)用 callAlloc 函數(shù)。
+ (id)alloc {
return _objc_rootAlloc(self);
}
// 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*/);
}
2.callAlloc
// 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__
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));
在這里我們也拓展一下其他知識(shí),了解一下objc底層一些特性:
- 先看一下ALWAYS_INLINE
inline 是一種降低函數(shù)調(diào)用成本的方法,其本質(zhì)是在調(diào)用聲明為 inline 的函數(shù)時(shí),會(huì)直接把函數(shù)的實(shí)現(xiàn)替換過去,這樣減少了調(diào)用函數(shù)的成本。 是一種以空間換時(shí)間的做法。
define ALWAYS_INLINE inline attribute((always_inline))
ALWAYS_INLINE宏會(huì)強(qiáng)制開啟inline。
- slowpath & fastpath
define fastpath(x) (__builtin_expect(bool(x), 1))
define slowpath(x) (__builtin_expect(bool(x), 0))
這兩個(gè)宏使用__builtin_expect函數(shù)
__builtin_expect(EXP, N)
這個(gè)指令的意思是EXP==N的概率更大。用于上方就是對(duì)于> fastpath(x) x為真可能性更大,對(duì)于> slowpath(x) x為假可能性更大。
這個(gè)指令的作用是允許程序員將最有可能執(zhí)行的分支告訴編譯器,用于優(yōu)化代碼執(zhí)行效率,提高預(yù)讀指令的命中率,以減少指令跳轉(zhuǎn)帶來(lái)的性能下降!
所以我們?cè)诜治龅臅r(shí)候,以實(shí)際為準(zhǔn)。
對(duì)于下面代碼:
if (fastpath(!cls->ISA()->hasCustomAWZ()))
此句代碼為判斷當(dāng)前class or superclass 是否有自定義的alloc/allocWithZone 方法實(shí)現(xiàn) ?
如果cls->ISA()->hasCustomAWZ()返回YES,意味著有自定義的allocWithZone方法實(shí)現(xiàn)。這個(gè)值會(huì)存儲(chǔ)在metaclass中。
雖然這里是fastpath,但是對(duì)于初次創(chuàng)建的類是還沒有默認(rèn)的實(shí)現(xiàn)的。所以繼續(xù)向下執(zhí)行進(jìn)入到msgSend消息發(fā)送流程,調(diào)用alloc函數(shù)。
3、alloc
+ (id)alloc {
return _objc_rootAlloc(self);
}
4、_objc_rootAlloc
// 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*/);
}
執(zhí)行到這個(gè)過程會(huì)再次進(jìn)入到 callAlloc 函數(shù),但是這一次當(dāng)前類的默認(rèn)實(shí)現(xiàn)已經(jīng)注冊(cè)完畢,所以會(huì)執(zhí)行到 _objc_rootAllocWithZone 函數(shù)中。
5.callAlloc
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
6. _objc_rootAllocWithZone
NEVER_INLINE
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);
}
7. _class_createInstanceFromZone
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);
}
先看一下前面的幾個(gè)bool值
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
- hasCxxCtor 判斷當(dāng)前class or superclass 是否有.cxx_construct構(gòu)造方法的實(shí)現(xiàn);
- hasCxxDtor 判斷當(dāng)前class or superclass 是否有.cxx_destruct析構(gòu)方法的實(shí)現(xiàn);
- canAllocNonpointer 是對(duì) isa 的類型的區(qū)分,如果一個(gè)類和它父類的實(shí)例不能使用 isa_t 類型的 isa 的話,返回值為 false。在 Objective-C 2.0 中,大部分類都是支持的;
7.1 重要函數(shù):計(jì)算所需空間大小:
size_t size = cls->instanceSize(extraBytes);
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;
}
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);
}
}
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
計(jì)算申請(qǐng)空間大小: 對(duì)于繼承自NSObject沒有任何屬性的自定義類而言,默認(rèn)有個(gè)isa結(jié)構(gòu)體。占8字節(jié)。
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
最終經(jīng)過 align16 函數(shù)計(jì)算方式:
(8 + 15)& ~15 (等同于>>4再<<4)
最終結(jié)果為16的倍數(shù),且最小為16字節(jié)(這里的傳入的x受類的屬性個(gè)數(shù)的影響)。在Core Foundation 中,需要所有的對(duì)象的大小不小于 16 字節(jié)。
補(bǔ)充知識(shí)點(diǎn):內(nèi)存對(duì)齊原則
1:數(shù)據(jù)成員對(duì)其規(guī)則(struct)(或聯(lián) 合(union))的數(shù)據(jù)成員,第
?個(gè)數(shù)據(jù)成員放在offset為0的地?,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要
從該成員??或者成員的?成員??(只要該成員有?成員,?如說(shuō)是數(shù)組,
結(jié)構(gòu)體等)的整數(shù)倍開始(?如int為4字節(jié),則要從4的整數(shù)倍地址開始存
儲(chǔ)。
2:結(jié)構(gòu)體作為成員:如果?個(gè)結(jié)構(gòu)?有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從
其內(nèi)部最?元素??的整數(shù)倍地址開始存儲(chǔ).(struct a?存有struct b,b
?有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲(chǔ).)
3:收尾?作:結(jié)構(gòu)體的總??,也就是sizeof的結(jié)果,.必須是其內(nèi)部最?
成員的整數(shù)倍.不?的要補(bǔ)?。
7.2 重要函數(shù):calloc
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
alloc 開辟內(nèi)存的地方。
按計(jì)算大小通過 calloc()函數(shù)為對(duì)象分配內(nèi)存空間, calloc()會(huì)默認(rèn)的把申請(qǐng)出來(lái)的空間初始化為0或者nil。(這里的zone基本是不會(huì)走的,蘋果廢棄了zone開辟空間,并且這里zone的入?yún)魅氲囊彩莕il)
補(bǔ)充知識(shí)點(diǎn):
函數(shù)malloc()和calloc()都可以用來(lái)動(dòng)態(tài)分配內(nèi)存空間,但兩者稍有區(qū)別
malloc()函數(shù)有一個(gè)參數(shù),即要分配的內(nèi)存空間的大小:
void *malloc(size_t size);
calloc()函數(shù)有兩個(gè)參數(shù),分別為元素的數(shù)目和每個(gè)元素的大小,這兩個(gè)參數(shù)的乘積就是要分配的內(nèi)存空間的大小。
void *calloc(size_t numElements,size_t sizeOfElement);
如果調(diào)用成功,函數(shù)malloc()和函數(shù)calloc()都將返回所分配的內(nèi)存空間的首地址。
函數(shù)malloc()和函數(shù)calloc()的主要區(qū)別是前者不能初始化所分配的內(nèi)存空間,而后者能。如果由malloc()函數(shù)分配的內(nèi)存空間原來(lái)沒有被使用過,則其中的每一位可能都是0;反之,如果這部分內(nèi)存曾經(jīng)被分配過,則其中可能遺留有各種各樣的數(shù)據(jù)。也就是說(shuō),使用malloc()函數(shù)的程序開始時(shí)(內(nèi)存空間還沒有被重新分配)能正常進(jìn)行,但經(jīng)過一段時(shí)間(內(nèi)存空間還已經(jīng)被重新分配)可能會(huì)出現(xiàn)問題。
經(jīng)常用memset()進(jìn)行清零
函數(shù)calloc()會(huì)將所分配的內(nèi)存空間中的每一位都初始化為零,也就是說(shuō),如果你是為字符類型或整數(shù)類型的元素分配內(nèi)存,那麼這些元素將保證會(huì)被初始化為0;如果你是為指針類型的元素分配內(nèi)存,那麼這些元素通常會(huì)被初始化為空指針;如果你為實(shí)型數(shù)據(jù)分配內(nèi)存,則這些元素會(huì)被初始化為浮點(diǎn)型的
- 函數(shù)聲明(函數(shù)原型):
void malloc(int size);
說(shuō)明:malloc 向系統(tǒng)申請(qǐng)分配指定size個(gè)字節(jié)的內(nèi)存空間。返回類型是 void 類型。void* 表示未確定類型的指針。C,C++規(guī)定,void* 類型可以強(qiáng)制轉(zhuǎn)換為任何其它類型的指針。從函數(shù)聲明上可以看出。malloc 和 new 至少兩個(gè)不同: new 返回指定類型的指針,并且可以自動(dòng)計(jì)算所需要大小。
比如:
int p;
p = new int; //返回類型為int 類型(整數(shù)型指針),分配大小為 sizeof(int);
或:
int* parr;
parr = new int [100]; //返回類型為 int* 類型(整數(shù)型指針),分配大小為 sizeof(int) * 100;
而 malloc 則必須由我們計(jì)算要字節(jié)數(shù),并且在返回后強(qiáng)行轉(zhuǎn)換為實(shí)際類型的指針。
int* p;
p = (int *) malloc (sizeof(int));
- 第一、malloc 函數(shù)返回的是 void * 類型,如果你寫成:p = malloc (sizeof(int)); 則程序無(wú)法通過編譯,報(bào)錯(cuò):“不能將 void* 賦值給 int * 類型變量”。所以必須通過 (int *) 來(lái)將強(qiáng)制轉(zhuǎn)換。
- 第二、函數(shù)的實(shí)參為 sizeof(int) ,用于指明一個(gè)整型數(shù)據(jù)需要的大小。如果你寫成:
int* p = (int ) malloc (1);代碼也能通過編譯,但事實(shí)上只分配了1個(gè)字節(jié)大小的內(nèi)存空間,當(dāng)你往里頭存入一個(gè)整數(shù),就會(huì)有3個(gè)字節(jié)無(wú)家可歸,而直接“住進(jìn)鄰居家”!造成的結(jié)果是后面的內(nèi)存中原有數(shù)據(jù)內(nèi)容全部被清空。
malloc 也可以達(dá)到 new [] 的效果,申請(qǐng)出一段連續(xù)的內(nèi)存,方法無(wú)非是指定你所需要內(nèi)存大小。
比如想分配100個(gè)int類型的空間:
int p = (int *) malloc ( sizeof(int) * 100 ); //分配可以放得下100個(gè)整數(shù)的內(nèi)存空間。
另外有一點(diǎn)不能直接看出的區(qū)別是,malloc 只管分配內(nèi)存,并不能對(duì)所得的內(nèi)存進(jìn)行初始化,所以得到的一片新內(nèi)存中,其值將是隨機(jī)的。除了分配及最后釋放的方法不一樣以外,通過malloc或new得到指針,在其它操作上保持一致
7.3重要函數(shù):initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
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;
}
}
初始化的過程就是對(duì)isa_t結(jié)構(gòu)體初始化的過程。
關(guān)于Isa類型
Isa類型有如下宏定義
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
// Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa
// field as a maskable pointer with other data around it.
#if (!__LP64__ || TARGET_OS_WIN32 || \
(TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC))
# define SUPPORT_PACKED_ISA 0
#else
# define SUPPORT_PACKED_ISA 1
#endif
// Define SUPPORT_NONPOINTER_ISA=1 on any platform that may store something
// in the isa field that is not a raw pointer.
#if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA
# define SUPPORT_NONPOINTER_ISA 0
#else
# define SUPPORT_NONPOINTER_ISA 1
#endif
SUPPORT_INDEXED_ISA 的含義是,如果為 1,則會(huì)在 isa field 中存儲(chǔ) class 在 class table 的索引。SUPPORT_PACKED_ISA 為 1 時(shí)則是在 isa field 中通過 mask 的方式存儲(chǔ) class 的指針信息。
SUPPORT_NONPOINTER_ISA 用于標(biāo)記是否支持優(yōu)化的 isa 指針,其字面含義意思是 isa 的內(nèi)容不再是類的指針了,而是包含了更多信息,比如引用計(jì)數(shù),析構(gòu)狀態(tài),被其他 weak 變量引用情況
在 _class_createInstanceFromZone 中,主要做了3件事,
- 1.計(jì)算對(duì)象所需的空間大小;
- 2.根據(jù)計(jì)算大小開辟空間;
- 3.初始化isa,使其與當(dāng)前對(duì)象關(guān)聯(lián)。
補(bǔ)充知識(shí)點(diǎn):isa流程圖

二、init函數(shù)
通過alloc方法,開辟了所需空間,關(guān)聯(lián)了isa,那么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;
}
init函數(shù)返回了當(dāng)前對(duì)象而已!那么蘋果如此設(shè)計(jì)的目的是什么呢?其實(shí)每個(gè)對(duì)象從出生并非是完全相同的,允許我們對(duì)各個(gè)對(duì)象進(jìn)行不同的配置,這就是init的函數(shù)的目的所在。
三、new函數(shù)
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
包裝了 alloc init,允許我們快速的生成一個(gè)對(duì)象。
四、總結(jié)
- alloc是孕育的過程,孕育完成之時(shí),對(duì)象的“遺傳基因”等信息就已經(jīng)確定了
- init是出生的過程,允許我們?cè)诔錾筮M(jìn)行不同的裝配。