OC底層原理三:探索alloc (你好,alloc大佬 )

OC底層原理 學習大綱

OC對象的始源 - alloc。

前言

我們都知道,創(chuàng)建OC對象的2種方式: [[ClassName alloc]init][ClassName new]

當被問起他們的作用時,可能你的回答是: alloc + init給對象開辟內存空間并完成對象初始化,new是類方法,實現(xiàn)的功能一樣。

這個描述沒有錯,但請詳細描述下他們的作用。

可能你一臉懵逼。 心里已經開罵: 你丫有病吧,我已經說完了呀?。?/strong>

今天,我將幫你扯開alloc、init、new的這塊遮羞布。 讓你深入了解alloc、init、new。

前期準備

課前問題

打開objc4-781包,在HTTest文件夾中,創(chuàng)建HTPerson測試文件(繼承自NSObject),切換項目target為HTTest,在main.m文件中加入測試代碼。

image.png

%@ 打印對象 %p 打印地址 &p 指針地址

問題:

  1. p1p2、p3對象和地址打印都一致, 為何&p打印不一致
  2. p4的地址為什么和p1、p2、p3都不一樣。

學完本章,你就徹底懂了

本節(jié)內容:

  1. alloc流程
  2. alloc核心函數
  3. alloc的地位(init、new)

1. alloc流程

打開源碼工程,跟隨alloc函數,一步步深入。流程如下:

alloc流程.png

當出現(xiàn)分支時,我們可以添加斷點,輔助查看主流程是進入哪個分支。

不知道打斷點,可參考OC底層原理一:定位源碼(歡迎來到底層世界)內的三種斷點技巧。

callAlloc處出現(xiàn)了分支。斷點后發(fā)現(xiàn)程序走向_objc_rootAllocWithZone分支,繼而進入_class_createInstanceFromZone函數。

image.png

關于fastpathslowpath的作用,請移步OC底層原理四: 編譯器優(yōu)化

allocwithZone: 和alloc一樣,為對象分配足夠的內存, cocoa 會遍歷該對象所有的成員變量,通過成員變量的類型來計算所需占用的內存。從iOS8以后,Zone外層API已被廢棄,僅底層源碼做兼容處理。

_class_createInstanceFromZone是最底層的包工頭。?? 終于找到真正干活的人了。它實現(xiàn)三大核心方法,然后將成品obj返回給外層。

_class_createInstanceFromZone核心方法.png

2.alloc核心函數

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. 計算開辟的內存大小
    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. 申請內存空間
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        // 初始化isa并與objc關聯(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.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        // 返回成品對象
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}
  • hasCxxCtor、hasCxxDtor、fast等 后續(xù)剖析isa會詳細講解
1. 計算內存大小: instanceSize
    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;
    }

if (size < 16) size = 16 :做了小于16字節(jié)的判斷。
跟斷點,發(fā)現(xiàn)主流程進入cache.fastInstanceSize(extraBytes)

 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);
        }
    }

繼續(xù)跟斷點,進入align16

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

align16的實現(xiàn),就是使用位運算算法完成16字節(jié)對齊。

算法(x + size_t(15)) & ~size_t(15)
x=8為例,計算過程如下:

  • 8 + size(15) = 23 二進制-> 0000 0000 0001 0111
  • size_t(15) 二進制-> 0000 0000 0000 1111
  • 取反~size_t(15) 二進制-> 1111 1111 1111 0000
  • 求交 & :
    0000 0000 0001 0111 & 1111 1111 1111 0000 = 0000 0000 0001 0000
  • 結果表示為十進制: 16

目的:

  • 提高性能,加快存儲速度
    通常內存是由一個個字節(jié)組成,cpu在存儲數據時,是以固定字節(jié)塊為單位進行存取的。這是一個空間換時間的優(yōu)化方式,這樣不用考慮字節(jié)未對齊的數據,極大節(jié)省了計算資源,提升了存取速度。
  • 更安全
    在一個對象中,isa占8字節(jié),對象屬性也占8字節(jié)。蘋果公司現(xiàn)在采用16字節(jié)對齊,當對象無屬性是,會預留8字節(jié),即16字節(jié)對齊。 如果不預留,CPU存取時以16字節(jié)為單位長度去訪問,會訪問到相鄰對象,造成訪問混亂。

執(zhí)行完后,回到上層函數size = cls->instanceSize(extraBytes)可打印size值。
此時已完成內存大小計算。

image.png
2. 分配內存 calloc

根據size 大小進行內存分配

image.png
  • 執(zhí)行前打印obj只有cls類名,執(zhí)行后打印,已成功申請內存首地址。

  • 但并不是我們想象中的格式<HTPerson: 0x10069eff0>,這是因為這一步只是單純的完成內存申請,返回首地址。

  • 類和地址的綁定是下一步initInstanceIsa的工作

3. initInstanceIsa

初始化isa,完成與類的綁定

obj->initInstanceIsa(cls, hasCxxDtor);
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

具體的isa結構和綁定關系,后續(xù)會作為單獨章節(jié)進行講解

isainit之后加斷點,打印obj,此時發(fā)現(xiàn)地址與類完成綁定

image.png

總結: 至此,我們已對alloc有了完整的認知

3. alloc的地位(init、new)

可能你有疑問,alloc把活都干完了,init和new干啥?

init

進入init

+ (id)init {
    return (id)self;
}

- (id)init {
    return _objc_rootInit(self);
}

進入_objc_rootInit

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)init的類方法和對象方法返回的都是id對象本身。
  • 不同的是類方法返回了一個id類型的self,這是為了可以給開發(fā)者提供自定義構造方法的入口,通過id強轉類型實現(xiàn)工廠設計,返回我們定義的類型。

new

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

實際上就是完成調用了callAlloc,走的時alloc流程。

唯一區(qū)別:

  • alloc + init允許對init進行重寫,可自定制init完成工廠設計
  • new是完整封裝,無法在初始化這一步加入自定制需求

答案

image.png
  • 問題1: p1、p2、p3對象和地址打印都一致, 為何&p打印不一致

alloc是真正開辟內存和綁定對象的,p1、p2、p3共用1個alloc,所以他們都是指向同一目的地址。但是他們本身也是對象,在init時傳入他們自身id,&p打印的是他們自身的地址。

通俗的說:
我有一個房子出售,A、B、C三個都是我員工,他們都領著客戶來看我這套房子。但是他們三個雖然都是我公司員工,但工號(id)不一樣。
如果客戶問他們房子在哪(等同于%@%p打印),他們都會告訴我房子的具體位置(三人說的一定相同)。
如果顧客問他們是誰(等同于打印&p),他們就會各自回答A、B、C。

  • 問題2. p4的地址為什么和p1、p2、p3都不一樣。

因為p1、p2、p3是同一個alloc打印的,而p4是new出來的,new會單獨調用alloc。 所以他們打印肯定不一樣

下一節(jié): OC底層原理五: NSObject的alloc分析

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

友情鏈接更多精彩內容