從原始碼角度看蘋果是如何實現 alloc、new、copy 和 mutablecopy 的

小橘爺發表於2019-05-13

在瞭解瞭如何在 MRC 和 ARC 兩種不同的環境下管理我們的記憶體之後,接下來讓我們從原始碼角度看一下,蘋果是如何實現 MRC 環境中的記憶體管理的相關方法的。

原始碼下載地址:opensource.apple.com/tarballs/ob…

alloc

先來看 alloc 方法在 NSObject 類中的實現:

+ (id)alloc {
    return _objc_rootAlloc(self);
}
複製程式碼

簡單的呼叫了 _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*/);
}
複製程式碼

_objc_rootAlloc 則呼叫了 callAlloc 函式,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 (slowpath(checkNil && !cls)) return nil; // 如果 checkNil 為 ture,且 cls 為 nil,直接返回 nil。

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) { // 如果 cls 沒有實現自定義 allocWithZone 方法
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) { // 如果 cls 支援快速 Alloc
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor(); // 獲取 cls 是否有自己的解構函式。
            id obj = (id)calloc(1, cls->bits.fastInstanceSize()); // 使用 calloc 根據 fastInstanceSize 的大小申請記憶體空間
            if (slowpath(!obj)) return callBadAllocHandler(cls); // 如果記憶體空間申請失敗,呼叫 callBadAllocHandler
            obj->initInstanceIsa(cls, dtor); // 如果成功,初始化 isa
            return obj; // 返回物件
        }
        else { // 如果 cls 不支援快速 Alloc
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);  // 通過 class_createInstance 方法直接建立物件
            if (slowpath(!obj)) return callBadAllocHandler(cls); // 如果物件建立失敗,呼叫 callBadAllocHandler
            return obj; // 返回物件
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];  // 如果 allocWithZone 為 true,呼叫 allocWithZone 建立物件
    return [cls alloc]; // 否則呼叫 alloc 方法建立物件
}
複製程式碼

整個方法的實現和程式碼解讀已經寫在註釋裡了,這裡面有很多知識點值得講一講:

ALWAYS_INLINE

inline 是一種降低函式呼叫成本的方法,其本質是在呼叫宣告為 inline 的函式時,會直接把函式的實現替換過去,這樣減少了呼叫函式的成本。當然 inline 是一種以空間換時間的做法,濫用 inline 會導致應用程式的體積增大,所以有的時候編譯器未必會真的按照你宣告 inline 的方式去用函式的實現替換函式的呼叫。

ALWAYS_INLINE 巨集如其名,會強制開啟 inline,其實現如下:

#define ALWAYS_INLINE inline __attribute__((always_inline))
複製程式碼

slowpath & fastpath

這兩個巨集的實現如下:

#define slowpath(x) (__builtin_expect(bool(x), 0))
#define fastpath(x) (__builtin_expect(bool(x), 1))
複製程式碼

這兩個巨集都使用了一個叫做 __builtin_expect 的函式:

long __builtin_expect (long EXP, long C)  
複製程式碼

它的返回值就是整個函式的返回值,引數 C 代表預計的值,表示程式設計師知道 EXP 的值很可能就是 C。

因此,在蘋果定義的兩個巨集中,fastpath(x) 依然返回 x,只是告訴編譯器 x 的值一般不為 0,從而編譯器可以進行優化。同理,slowpath(x) 表示 x 的值很可能為 0,希望編譯器進行優化。

因為計算機每次不會只讀取一條語句,所以上述兩個巨集在 if 條件判斷中很有用,有助於編譯器優化程式碼,以減少指令重讀。

OBJC2

用於判斷當前使用的語言是否是 Objective-C 2.0

如何獲取類物件的 isa

在之前的方法中,cls 的 isa 是通過 cls->ISA() 獲取的,在解讀方法的實現之前,先來學習一下 SUPPORT_NONPOINTER_ISA 巨集的含義。

// 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_NONPOINTER_ISA 用於標記是否支援優化的 isa 指標,其字面含義意思是 isa 的內容不再是類的指標了,而是包含了更多資訊,比如引用計數,析構狀態,被其他 weak 變數引用情況。而 SUPPORT_NONPOINTER_ISA 巨集的定義又涉及到了其他兩個巨集:SUPPORT_INDEXED_ISA 和 SUPPORT_PACKED_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
複製程式碼

從註釋上來看,SUPPORT_INDEXED_ISA 的含義是,如果為 1,則會在 isa field 中儲存 class 在 class table 的索引。SUPPORT_PACKED_ISA 為 1 時則是在 isa field 中通過 mask 的方式儲存 class 的指標資訊。

接下來讓我們來看一下 ISA 方法的實現:

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer());  // 要確保該類不是 tagged pointer
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) { // 如果 isa 是非指標型 isa
        uintptr_t slot = isa.indexcls; // 獲取 class 索引
        return classForIndex((unsigned)slot); // 從 class table 中查詢並返回 isa 資訊
    }
    return (Class)isa.bits; // 直接返回 isa.bits
#else
    return (Class)(isa.bits & ISA_MASK); // 通過掩碼獲取 bits 中對應的資訊並返回
#endif
}
複製程式碼

如何判斷類是否實現了 allocWithZone 方法

hasCustomAWZ 方法的實現如下

bool hasCustomAWZ() {
    return ! bits.hasDefaultAWZ();
}
複製程式碼

hasDefaultAWZ 方法的實現如下:

bool hasDefaultAWZ() {
    return data()->flags & RW_HAS_DEFAULT_AWZ;
}
複製程式碼

而 data 方法的實現是:

class_rw_t* data() {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}
複製程式碼

總而言之就是從 bits 中根據位掩碼 FAST_DATA_MASK 獲取 data,再根據位掩碼 RW_HAS_DEFAULT_AWZ 獲取是否是預設的 allocWithZone 方法。

如何判斷類是否支援快速 Alloc

canAllocFast 使用來獲取類是否支援快速 alloc 的方法,其實現如下:

bool canAllocFast() {
    assert(!isFuture());
    return bits.canAllocFast();
}
複製程式碼

我們先來看 isFuture 的實現:

// Returns true if this is an unrealized future class.
// Locking: To prevent concurrent realization, hold runtimeLock.
bool isFuture() { 
    return data()->flags & RW_FUTURE;
}
複製程式碼

bits 的 canAllocFast 方法實現如下,同樣是通過位掩碼 FAST_ALLOC 從 bits 中獲取資訊:

// summary bit for fast alloc path: !hasCxxCtor and 
//   !instancesRequireRawIsa and instanceSize fits into shiftedSize
#define FAST_ALLOC              (1UL<<2)

#if FAST_ALLOC
    bool canAllocFast() {
        return bits & FAST_ALLOC;
    }
#else
    bool canAllocFast() {
        return false;
    }
#endif
複製程式碼

hasCxxDtor

hasCxxDtor 方法的作用是獲取當前 Class 是否有自己的解構函式。hasCxxDtor 方法用來獲取類及父類是否有自己的解構函式,與物件是否有例項變數有關,會記錄在物件的 isa 內。實現如下:

bool hasCxxDtor() {
    // addSubclass() propagates this flag from the superclass.
    assert(isRealized());
    return bits.hasCxxDtor();
}
複製程式碼

isRealized 類似與 isFuture,實現如下:

// Locking: To prevent concurrent realization, hold runtimeLock.
bool isRealized() {
    return data()->flags & RW_REALIZED;
}
複製程式碼

bits 的 hasCxxDtor 方法實現如下:

// class or superclass has .cxx_destruct implementation
#define FAST_HAS_CXX_DTOR       (1UL<<51)

#if FAST_HAS_CXX_DTOR
bool hasCxxDtor() {
    return getBit(FAST_HAS_CXX_DTOR);
}
#else
bool hasCxxDtor() {
    return data()->flags & RW_HAS_CXX_DTOR;
}
#endif
複製程式碼

為物件申請記憶體空間

接著,通過 calloc 函式,用來動態地分配記憶體空間並初始化為 0。calloc 函式原型如下:

void *calloc(size_t __count, size_t __size)
複製程式碼

calloc 在記憶體中動態地分配 num 個長度為 size 的連續空間,並將每一個位元組都初始化為 0。所以它的結果是分配了 num * size 個位元組長度的記憶體空間,並且每個位元組的值都是0。分配成功返回指向該記憶體的地址,失敗則返回 NULL。

cls->bits.fastInstanceSize() 是用來獲取例項佔用儲存空間的方法,實現如下:

size_t fastInstanceSize() 
{
    assert(bits & FAST_ALLOC);
    return (bits >> FAST_SHIFTED_SIZE_SHIFT) * 16;
}
複製程式碼

接著會判斷物件是否建立成功,如果沒能建立成功會呼叫 callBadAllocHandler(cls),

static id defaultBadAllocHandler(Class cls)
{
    _objc_fatal("attempt to allocate object of class '%s' failed", 
                cls->nameForLogging());
}

static id(*badAllocHandler)(Class) = &defaultBadAllocHandler;

static id callBadAllocHandler(Class cls)
{
    // fixme add re-entrancy protection in case allocation fails inside handler
    return (*badAllocHandler)(cls);
}
複製程式碼

初始化 isa

如果物件建立成功,會使用 initInstanceIsa 為其初始化 isa,程式碼如下:

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.cls = cls; // 如果 nonpointer 為 false,則直接把 cls 賦值給 cls
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0); // 初始化新的 isa

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE; // 對 isa 中的一些值進行初始化
        // 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; // 將 newisa 賦值給 isa
    }
}
複製程式碼

class_createInstance

如果類不支援 AllocFast,則需要通過 class_createInstance 方法進行物件的建立,方法的實現如下:

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

/***********************************************************************
* class_createInstance
* fixme
* Locking: none
**********************************************************************/

static __attribute__((always_inline)) 
id
_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(); // 獲取 cls 及其父類是否有建構函式
    bool hasCxxDtor = cls->hasCxxDtor(); // 獲取 cls 及其父類是否有解構函式
    bool fast = cls->canAllocNonpointer(); // 是對 isa 的型別的區分,如果一個類和它父類的例項不能使用 isa_t 型別的 isa 的話,返回值為 false,但是在 Objective-C 2.0 中,大部分類都是支援的

    size_t size = cls->instanceSize(extraBytes); // 獲取需要申請的空間大小
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) { // 如果 zone 引數為空,且支援 fast,通過 calloc 申請空間並初始化 isa
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) { // 如果 zone 不為空,使用 malloc_zone_calloc 方法申請空間
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else { // 如果 zone 為空,使用 calloc 方法申請空間
            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); // 初始化 isa
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls); // 構建物件
    }

    return obj;
}
複製程式碼

allocWithZone

如果是非 Objective-C 2.0 的程式碼,且 allocWithZone 為 true,會通過 allocWithZone 方法初始化物件,其實現如下所示:

// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif

    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}
複製程式碼

new

再講完 alloc 後,new 的實現就很簡單了,如下所示:

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}
複製程式碼

就是巢狀呼叫了 alloc 和 init。

copy & mutableCopy

copy 和 mutableCopy 的實現就很簡單了,它們只是呼叫了 NSCopying 協議中的方法:

- (id)copy {
    return [(id)self copyWithZone:nil];
}

- (id)mutableCopy {
    return [(id)self mutableCopyWithZone:nil];
}
複製程式碼

至此,關於 alloc、new、copy 和 mutableCopy 方法的實現就已經分析完了,關於類的一些細節,可以參考擴充套件閱讀裡的文章進行更深一步的學習。

擴充套件閱讀:

相關文章