問題:在iOS開發(fā)中,我們寫的最多的可能就是以下代碼
Car *car = [[Car alloc] init];
Car *car = [Car new];
創(chuàng)建對象必須要調(diào)用的方法。但是,你知道他們的區(qū)別,以及分別具有什么作用嗎?為什么要alloc init 一起使用?對象的本質(zhì)是什么?
接下來我們從源碼角度對以上幾個函數(shù)進(jìn)行分析
1.準(zhǔn)備工作
源碼下載:objc4-781
源碼編譯:可參考教程 源碼編譯調(diào)試
2.源碼分析
2.1 alloc源碼
源碼在手,天下我有!接著就很簡單了,創(chuàng)建一個Person類,然后調(diào)用 alloc 方法,main.m 如下:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *objc = [Person alloc];
NSLog(@"Hello, World! %@", objc);
}
return 0;
}
此時,按住command 鼠標(biāo)左鍵就可以跳入alloc方法內(nèi)部了
【第一步】NSObject.mm中的 alloc 實(shí)現(xiàn),可以看到alloc內(nèi)部調(diào)用了一個叫 _objc_rootAlloc 的函數(shù),并傳入了當(dāng)前Person類
+ (id)alloc {
return _objc_rootAlloc(self);
}
【第二步】_objc_rootAlloc內(nèi)部又調(diào)用了 callAlloc() ,同樣傳入了Person類
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
【第三步】callAlloc函數(shù)內(nèi)部
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ù)出現(xiàn)了分支。因此,在流程判斷上,就會復(fù)雜一些不過,我們編譯了源碼,根據(jù)斷點(diǎn)很容易判斷流程走了 _objc_rootAllocWithZone() 函數(shù)
這里解釋下為什么會走 _objc_rootAllocWithZone() ?
我們的 NSObject 類經(jīng)歷過改版,原本會調(diào)用allocWithZone:方法,改版后去掉了。
【第四步】接下來,_objc_rootAllocWithZone() 將我們的流程帶入到了objc-runtime-new.mm文件中
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);
}
【第五步】_objc_rootAllocWithZone() 內(nèi)部進(jìn)入到了alloc源碼的核心操作 _class_createInstanceFromZone() 函數(shù),也就是這部分代碼,可以解答文章開始提出的問題
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());
// Person類或者父類是否有C++構(gòu)造方法或析夠方法
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
//是否能alloc nonpointer isa
bool fast = cls->canAllocNonpointer();
size_t size;
//計算對象所需大學(xué),這里extraBytes == 0,由前一函數(shù)傳遞
size = cls->instanceSize(extraBytes);
//將計算好的size通過指針地址傳遞出去,不影響我們理解主流程,可以忽略
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
//判斷是否有zone,在之前的版本是有zone的
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// alloc 開辟內(nèi)存的地方
obj = (id)calloc(1, size);
}
//這里是內(nèi)存開辟錯誤執(zhí)行的流程,字面意思可以看到,系統(tǒng)調(diào)用了alloc失敗句柄
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
// 將類與 isa 關(guān)聯(lián)
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
//老版本的內(nèi)存分配
obj->initIsa(cls);
}
//若沒有C++構(gòu)造函數(shù),則將關(guān)聯(lián)好類的isa返回
if (fastpath(!hasCxxCtor)) {
return obj;
}
//若有c++構(gòu)造函數(shù),則走c++構(gòu)造流程
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
這段代碼我寫了注釋,通過源碼的閱讀,我們發(fā)現(xiàn)這里執(zhí)行了三個重要的操作,分別是:
-
cls->instanceSize():計算需要開辟的內(nèi)存空間大小。 -
calloc():申請內(nèi)存,返回地址指針。 -
obj->initInstanceIsa():將類與內(nèi)存以及指針進(jìn)行關(guān)聯(lián)。
接下來我們分別看一下這三個函數(shù)。
- 首先,第一個函數(shù),
size = cls->instanceSize(extraBytes);這里cls就是我們之前傳進(jìn)來的參數(shù)Person類。因此,此時我們已經(jīng)來到了objc_class對象中(詳情請參考runtime中class的數(shù)據(jù)結(jié)構(gòu))
size_t instanceSize(size_t extraBytes) const {
//判斷能否快速查找需要開辟的內(nèi)存空間大小(查找是否有緩存數(shù)據(jù))
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
//無法快速查找,alignedInstanceSize()計算
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
// 內(nèi)存大小最小為16,不足的會補(bǔ)齊
if (size < 16) size = 16;
return size;
}
- 根據(jù)斷點(diǎn)判斷,這里調(diào)用了
cache.fastInstanceSize(),cache是objc_runtime_new.mm 中的一個結(jié)構(gòu)體。
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
//GCC的內(nèi)建函數(shù) __builtin_constant_p 用于判斷一個值是否為編譯時常數(shù),如果參數(shù)EXP 的值是常數(shù),函數(shù)返回 1,否則返回 0
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
//類所需的內(nèi)存大小已經(jīng)存在于_flags中了,與一個常數(shù)做&運(yùn)算就是size的精準(zhǔn)大小
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
//刪除由setFastInstanceSize添加的FAST_CACHE_ALLOC_DELTA16 8個字節(jié)
//之后做16位字節(jié)對齊,也就是按16取整
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
-
align16()具體實(shí)現(xiàn),優(yōu)化過的取整運(yùn)算,之后就得到了我們要分配的內(nèi)存大小
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
- 然后再來看看無法快速查找的流程
alignedInstanceSize(),這里,系統(tǒng)進(jìn)行了兩步操作,分別是計算了Person類占用大小,然后進(jìn)行了字節(jié)對齊算法。
uint32_t alignedInstanceSize() const {
//第一步,計算對象要占用的內(nèi)存大小
//第二步,對內(nèi)存大小進(jìn)行字節(jié)對齊算法,這里是8字節(jié)對齊
return word_align(unalignedInstanceSize());
}
-
unalignedInstanceSize(), 其實(shí)這個函數(shù)比較簡單,就是調(diào)用了逐級查找,最終找到了 instanceSize ,另一方面也說明了instanceSize在類的初始化過程中已經(jīng)計算好了。
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
其次,是 calloc() 內(nèi)存空間的申請,這個函數(shù)在_malloc中。這個函數(shù)做的工作是分配一塊大小為size的內(nèi)存,并賦值給obj,因此 obj是指向內(nèi)存地址的指針。
obj = (id)calloc(1, size);
最后是obj->initInstanceIsa(),這個函數(shù)負(fù)責(zé)將類與內(nèi)存以及指針進(jìn)行關(guān)聯(lián),具體如何關(guān)聯(lián),我們點(diǎn)進(jìn)去看一下,這里,來到了objc_object.h 當(dāng)中,這也表示著,我們上一步得到的 obj 是一個objc_object類型的對象。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
initInstanceIsa()函數(shù)內(nèi)部又調(diào)用了initIsa(cls, true, hasCxxDtor);
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
//判斷是否是nonpointer類型對象,根據(jù)斷點(diǎn)判斷,我們的對象是nonpointer類型,因此走了else分支
if (!nonpointer) {
//利用cls初始化了一個isa_t的聯(lián)合體,里面的代碼并沒有開源
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
//主流程
//新聲明了一個isa_t類型的變量newisa,之后進(jìn)行賦值
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;
//重點(diǎn):shiftcls存放了類對象的指針,這里是賦值操作,從newisa的第3~35位,為類對象指針地址
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
isa = newisa;
}
}
這里是對isa的賦值操作,其實(shí)類對象的地址存在了isa的3~35位,詳情請看nonpointerisa介紹。通過斷點(diǎn)調(diào)試來印證上面的說法,在執(zhí)行完initInstanceIsa后,在通過po obj可以得出一個對象指針

總結(jié):
通過對 alloc 源碼分析,我們了解到,alloc 開辟內(nèi)存的核心步驟有3步,分別是
計算size -> 開辟內(nèi)存 -> 關(guān)聯(lián)對象
2.2 init源碼
alloc源碼分析完后,我們來看一下 init 源碼。出乎意料的發(fā)現(xiàn),init方法不只有實(shí)例方法,還有一個類方法 + (id)init,因此我們先來看一下init類方法實(shí)現(xiàn)。
+ (id)init {
return (id)self;
}
試一下直接調(diào)用,系統(tǒng)拋出了錯誤異常,說不能這么調(diào)用,呃 ...... 不好意思,打擾了,當(dāng)我沒有看見。
還是看一下init實(shí)例方法吧
- (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;
}
發(fā)現(xiàn),依然是返回 self ,所以,這里的 init 不起任何作用,這里單純的是一種工廠設(shè)計模式。系統(tǒng)開發(fā)了一個 init 的初始化函數(shù),供我們在對象初始化是執(zhí)行一些操作。
2.3 new源碼
一般在開發(fā)中,初始化除了init,還可以使用new,兩者本質(zhì)上并沒有什么區(qū)別,以下是objc中new的源碼實(shí)現(xiàn),通過源碼可以得知,new函數(shù)中直接調(diào)用了callAlloc函數(shù)(即alloc中分析的函數(shù)),且調(diào)用了init函數(shù),所以可以得出new 其實(shí)就等價于 [[Person alloc] init]的結(jié)論。
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
3.總結(jié)
本篇文章從源碼角度探索了 alloc/ init/ new三個方法的內(nèi)部實(shí)現(xiàn)
- alloc: 主要負(fù)責(zé)了計算要開辟的內(nèi)存大小,開辟內(nèi)存,以及關(guān)聯(lián)類。
- init:并沒有執(zhí)行任何操作,只是系統(tǒng)為我們提供的一個初始化的入口
- new: 等同于
[[Class alloc] init]操作,可理解我alloc init的一個簡寫。