iOS底層原理 - alloc & init & new 詳解

問題:在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可以得出一個對象指針

1603162993810.jpg

總結(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的一個簡寫。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容