iOS底層探究-alloc/init做了什么?

前言

想必大家做開發(fā)的時候敲過無數(shù)遍 [[XXX alloc] init],那么alloc函數(shù)到底做了什么工作知道嗎,不就是對象初始化開辟內(nèi)存嘛,那你能詳細(xì)點(diǎn)描述具體流程嗎?這時候就一臉懵逼,what?今天筆者就帶大家來探索下alloc函數(shù)具體做了什么。
大家先來看下面代碼打印的日志,分別打印了p1p2p3三個指針指向的內(nèi)容,指向的內(nèi)存地址,指針地址。

圖1

我們發(fā)現(xiàn)這三個指針指向的內(nèi)容和內(nèi)存地址是一樣的,由p1p2可以看出alloc后對象就已經(jīng)創(chuàng)建一個對象并且分配好內(nèi)存了,接下來init函數(shù)似乎只是初始化一個指針來指向alloc創(chuàng)建的對象。那么我們接下來就通過源碼去探索alloc/init做了什么。

準(zhǔn)備

開始追蹤objc源碼查看alloc具體操作

  • 第一步 點(diǎn)擊main函數(shù)里面LGperson類的alloc方法跳轉(zhuǎn)進(jìn)入圖2所示的alloc實(shí)現(xiàn)


    圖2
  • 第二步 繼續(xù)點(diǎn)擊return _objc_rootAlloc(self);跳轉(zhuǎn)下去到達(dá)_objc_rootAlloc的實(shí)現(xiàn)方法
// 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*/);
}
  • 第三步 繼續(xù)點(diǎn)擊跳轉(zhuǎn)至callAlloc(Class cls, bool checkNil, bool allocWithZone=false)源碼實(shí)現(xiàn)
// 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__  //objc有兩個版本 ,OBJC2為編譯優(yōu)化版本
    if (slowpath(checkNil && !cls)) return nil;
    //判斷這個類是否有自定義的 +allocWithZone 實(shí)現(xiàn),沒有則走到if里面的實(shí)現(xiàn)
    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));
}

我們編譯斷點(diǎn)調(diào)試發(fā)現(xiàn)走到了if __OBJC2__里面,調(diào)用的_objc_rootAllocWithZone

  • 第四步 然后我們就繼續(xù)點(diǎn)擊_objc_rootAllocWithZone函數(shù)跳轉(zhuǎn)發(fā)現(xiàn)就一行代碼
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);
}
  • 第五步 我們繼續(xù)點(diǎn)擊_class_createInstanceFromZone跳轉(zhuǎn),然后終于來到了alloc核心實(shí)現(xiàn)
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;
    // 1:要開辟多少內(nèi)存
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // 2;怎么去申請內(nèi)存
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    // 3: 將 cls類 與 obj指針(即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);
}

我們發(fā)現(xiàn)alloc做了三件事

  1. cls->instanceSize(extraBytes); 計算需要開辟的內(nèi)存空間大小。
  2. calloc 根據(jù)size申請內(nèi)存,然后返回該內(nèi)存地址的指針。
  3. obj->initInstanceIsa 將 cls類 與 obj指針(即isa) 關(guān)聯(lián)。

接下來我們看看init做了什么

  • 第一步 點(diǎn)擊LGPerson的init函數(shù)跳轉(zhuǎn),發(fā)現(xiàn)調(diào)用了_objc_rootInit
    init實(shí)現(xiàn)
  • 第二步 然后我們繼續(xù)點(diǎn)擊_objc_rootInit函數(shù)跳轉(zhuǎn),我們發(fā)現(xiàn)驚人的事實(shí),它竟然什么都沒做!直接return了傳進(jìn)來的obj對象self。
    _objc_rootInit函數(shù)實(shí)現(xiàn)

    那么為什么init里面啥都不做還要封裝一層呢?這不是廢操作嘛,當(dāng)然不是,這是蘋果遵循工廠模式給我們開發(fā)者封裝的,比如我們初始化對象的時候有些屬性我們得傳進(jìn)來賦值,那么init此時就有作用了,我們可以重寫init函數(shù)對對象做些操作,類似于下面
- (id)init {
  if (self = [super init]) {
    self.name = @"張三";
  }
  return self;
}

所以整個alloc/init大致流程如下


alloc/init流程

總結(jié)

一句話概括就是alloc做了三件事1. cls->instanceSize(extraBytes);計算對象需要多大內(nèi)存空間。2.調(diào)用calloc函數(shù)申請內(nèi)存并返回內(nèi)存的指針地址。3.obj->initInstanceIsa 將 cls類 與 obj指針(即isa) 關(guān)聯(lián)。 init做了一件事就是返回當(dāng)前對象。

對于第二步 calloc 根據(jù)size申請內(nèi)存,然后返回該內(nèi)存地址的指針。和第三步 obj->initInstanceIsa 將 cls類 與 obj指針(即isa) 關(guān)聯(lián),比較簡單明了就不多說了,下一遍博客主要講下第一步cls->instanceSize(extraBytes); 計算需要開辟的內(nèi)存空間大小。里面涉及到內(nèi)存字節(jié)對齊和為什么蘋果要這樣做。

最后編輯于
?著作權(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ù)。

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