001--alloc、init、new底層實(shí)現(xiàn)流程

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

image.png
image.png
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流程圖
截屏2020-06-11 下午2.34.32.png

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

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