iOS底層探索二(OC 中 alloc 方法 初探)

前言      

     相關文章:   

      iOS底層探索一(底層探索方法)

      iOS底層探索三(內存對齊與calloc分析)

      iOS底層探索四(isa初探-聯合體,位域,內存優化)

      iOS底層探索五(isa與類的關係)  

iOS底層探索六(類的分析上)

iOS底層探索七(類的分析下)

iOS底層探索八(方法本質上)

iOS底層探索九(方法的本質下objc_msgSend慢速及方法轉發初探)

iOS底層探索十(方法的本質下-消息轉發流程)

開發中我們經常會用到XZPerson *p= [[XZPerson alloc]init],我們只知道,這樣我們就新建了一個對象,可以直接使用這個對象,可以對這個對象進行賦值使用,但是我們沒有關心過alloc方法底層到底是通過什麼方式進行實現的;今天我準備對alloc方法進行一次嘗試性底層挖掘;首先我們需要先準備一份可編譯的objc4_752代碼,可以直接進行下載探索;

首先,先上一張我探索完成的alloc流程圖,供大家進行參考

 

 

然後我們直接使用能運行 objc-752 源碼後,在 main.m 中寫入如下代碼就開始了 alloc 的探索。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        XZPerson *object = [XZPerson alloc];
        NSLog(@"Hello, World! %@",object);

    }
    return 0;
}

打斷點直接這裏 alloc 點擊進入的爲 _objc_rootAlloc 其實我們斷點後發現進入的是 objc_alloc

這裏我們稍作解釋:我們可以看到源碼中有這麼一個方法fixupMessageRef,這裏應該是對進行符號綁定將alloc綁定爲objc_alloc方法,具體是通過LLVM期進行符號綁定,我們這篇文章先不進行詳細介紹;

fixupMessageRef(message_ref_t *msg)
{    
    msg->sel = sel_registerName((const char *)msg->sel);

    if (msg->imp == &objc_msgSend_fixup) { 
        if (msg->sel == SEL_alloc) {
            msg->imp = (IMP)&objc_alloc;
        } else if (msg->sel == SEL_allocWithZone) {
            msg->imp = (IMP)&objc_allocWithZone;
        } else if (msg->sel == SEL_retain) {
            msg->imp = (IMP)&objc_retain;
        } else if (msg->sel == SEL_release) {
            msg->imp = (IMP)&objc_release;
        } else if (msg->sel == SEL_autorelease) {
            msg->imp = (IMP)&objc_autorelease;
        } else {
            msg->imp = &objc_msgSend_fixedup;
        }
    } 
    else if (msg->imp == &objc_msgSendSuper2_fixup) { 
        msg->imp = &objc_msgSendSuper2_fixedup;
    } 
    else if (msg->imp == &objc_msgSend_stret_fixup) { 
        msg->imp = &objc_msgSend_stret_fixedup;
    } 
    else if (msg->imp == &objc_msgSendSuper2_stret_fixup) { 
        msg->imp = &objc_msgSendSuper2_stret_fixedup;
    } 
#if defined(__i386__)  ||  defined(__x86_64__)
    else if (msg->imp == &objc_msgSend_fpret_fixup) { 
        msg->imp = &objc_msgSend_fpret_fixedup;
    } 
#endif
#if defined(__x86_64__)
    else if (msg->imp == &objc_msgSend_fp2ret_fixup) { 
        msg->imp = &objc_msgSend_fp2ret_fixedup;
    } 
#endif
}

1.首先我們可以看到進入的是 alloc 內部調用_objc_rootAlloc方法績效調用callAlloc方法

+ (id)alloc {
    return _objc_rootAlloc(self);
}

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

這裏我們可以看到進入_objcrootAlloc方法裏面有參數爲Class的一個參數

Class 類底層其實是objc_class類結構體
typedef struct objc_class *Class;

objc_class結構體中
struct objc_class : objc_object {
   // Class ISA;         //isa指針
    Class superclass;  //父類
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    //...下面還有很多方法就不在這裏貼了,可以自行下代碼進行查看

objc_class 又繼承於 objc_object ,objc_object 存儲了 isa 的一個 Class 結構體

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

這裏如果有使用過RunTime的同學就可以看到一些熟悉的東西 ivars, methodLists ,protocols,這個後期文章我們再進行贅述,此篇文章先進行稍微引入

2.當進入callAlloc方法中

這裏我都有備註判斷的原因,如有不對還請看到的各位,評論留言我們可以一起探討,這裏如果我們自己打斷點的話,可以看到大部分會直接走到class_createInstance方法中;

callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    /**
     fastpath(x)表示x很可能不爲0,希望編譯器進行優化;
    slowpath(x)表示x很可能爲0,希望編譯器進行優化——這裏表示cls大概率是有值的,編譯器可以不用每次都讀取 return nil 指令
     */
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    /**
     實際意義是hasCustomAllocWithZone——這裏表示有沒有alloc / allocWithZone的實現(只有不是繼承NSObject/NSProxy的類才爲true)
     */
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        /**

         內部調用了bits.canAllocFast後分爲2種情況
        if(FAST_ALLOC){
          }else{
             默認爲false,
         }
         當我們查看FAST_ALLOC宏時,發現它的上方有個else 1 之後纔是else中定義這個宏,
        也就是說這個宏一直是不存在的
        可以自行點擊進入查看
         */
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

3.class_createInstance方法中,跟蹤class_createInstance(cls, 0)方法,其內部調用了_class_createInstanceFromZone方法,並在其中進行size計算,內存申請,以及isa初始化

class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

_class_createInstanceFromZone這個方法是核心方法,進行了開闢內存和關聯對象

_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
   // 獲取需要開闢的內存大小
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        //這裏是真正的創建類的方法但是這個calloc源碼是在malloc中
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        //將內存指向給objc進行關聯
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

其中cls->instanceSize(extraBytes)此方法爲計算應該開闢多大內存方法,計算出size,
其中64位系統下,對象大小採用8字節對齊,但是實際申請的內存最低爲16字節,也就是說系統分配內存按照16字節對齊分配

 size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;//最小返回16字節
        return size;
  }

 uint32_t alignedInstanceSize() {
        //字節對齊
        return word_align(unalignedInstanceSize());
  }

#   define WORD_MASK 7UL  //這個宏標示數字7
static inline uint32_t word_align(uint32_t x) {
    //WORD_MASK  7
    //7+8 = 15
    //0000 0111 2進製表示7
    //0000 1111 2進製表示15
    //1111 1000 2進製表示~7
    //0000 1000 15 & ~7 值 16
    //這裏主要就是做8進制對齊,這裏相當於8倍數
    //x + 7
    //x=1  8
    //x=2  16
    return (x + WORD_MASK) & ~WORD_MASK;
}

字節對齊核心算法:

例如傳值進來爲7 已知 WORD_MASK爲7

   //WORD_MASK  7
    7+8 = 15
    0000 0111        2進製表示7
    0000 1111         2進製表示15
    1111 1000         2進製表示~7

---------------------------------
    /0000 1000    (15 & ~7 ) 值 16

  這裏主要就是做8進制對齊,這裏相當於8倍數,當然這裏的WORD_MASK如果是15那麼這個就是16字節對齊

  根據不同的條件,使用calloc或者malloc_zone_calloc進行內存申請,並且初始化isa指針,至此size大小的對象obj已經申請完成,並且返回;

4.這就OC中alloc底層的調用方式,我們都看了alloc的源碼了,順便看下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方法中蘋果開發人員沒有做任何東西,只是將對象直接返回,說明這只是蘋果開發人員爲我們提供的一個工廠方法,讓我們方便進行重寫;

5.順便把New方法也看下

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

仔細看過alloc源碼的話,這裏就很清晰了,callAlloc不就是調用了下alloc方法的底層麼,順便也調用了下init,這就是說,new方法底層也是調用alloc的,

 

總結

這是個人的實際追蹤源碼得出的一些結論,源碼中還有一些分支代碼可以自行查看initInstanceIsa下篇文章在進行分析,如果有錯誤的地方還請指正,大家一起討論,開發水平一般,還望大家體諒,歡迎大家點贊,關注我的CSDN,我會定期做一些技術分享!

寫給自己:

研究源碼枯燥的,但是面對源碼不用害怕,一步步把它拆分開來研究,多看看官方給的註釋/Github大神的註釋,慢慢來,總有一自己也會厲害的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章