ObjC runtime原始碼 閱讀筆記(一)

mmmmmmaxx發表於2016-10-25

ObjC runtime原始碼 閱讀筆記(一)

我的部落格Max`s Blog
本文的原始碼來自於apple opensource


1.objc-private.h

開啟標頭檔案就看到了兩個熟悉的結構體指標

typedef struct objc_class *Class;
typedef struct objc_object *id;

我們會經常用到id這個指標,比較老的Foundation框架中,一般的初始化方法都會返回一個id物件,並且一些有iOS程式設計經驗的老鳥也會說ObjC中的所有物件都可以強轉成id型別。那麼現在就來分析一下id究竟是個什麼東東。

從原始碼來看真的是好長的一坨結構體,首先看到的是一個isa_tunion

private:
    isa_t isa;

這個isa_t用一句話概括就是:

對64位的裝置物件進行類物件指標的優化,利用合理的bit(arm64裝置為32位)儲存類物件的地址,其他位用來進行記憶體管理。這種優化模式被稱為tagged pointer。用在isa_t的實現中稱作IndexedIsa

下面是isa_tarm64架構下的結構:

union isa_t 
{
    uintptr_t bits;
    
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
};
  • indexed:標記是否啟動指標優化

  • has_assoc:是否有關聯物件

  • has_cxx_dtor:是否有析構器

  • shiftcls:類物件指標

  • magic:標記初始化完成

  • weakly_refrenced:是否弱引用

  • deallocating:是否正在釋放

  • extra_rc:引用計數(但是比retaincount小1)

至此,優化情況下的isa_t包含的內容大體總結完畢。
回過頭來繼續分析objc_object內的函式。

Class ISA() //不支援tagged pointer時候獲取Class的函式
Class getIsa() //支援tagged pointer時候獲取Class的函式

下面是一系列isa初始化的函式:

void initIsa(Class cls /*indexed=false*/);
void initClassIsa(Class cls /*indexed=maybe*/);
void initProtocolIsa(Class cls /*indexed=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);

值得注意的是這幾個函式最後呼叫的都是

inline bool objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 

下面的函式是用來改變一個物件的Class:

Class changeIsa(Class newCls);

印象中KVO的實現中,會改變一個物件的Class,以後會帶來驗證。
接下來的一系列函式顧名思義,我也不做解釋,內部的實現基本也都是依賴於isa來進行的。

    // changeIsa() should be used to change the isa of existing objects.
    // If this is a new object, use initIsa() for performance.
    Class changeIsa(Class newCls);

    bool hasIndexedIsa();
    bool isTaggedPointer();
    bool isClass();

    // object may have associated objects?
    bool hasAssociatedObjects();
    void setHasAssociatedObjects();

    // object may be weakly referenced?
    bool isWeaklyReferenced();
    void setWeaklyReferenced_nolock();
    
    // object may have -.cxx_destruct implementation?
    bool hasCxxDtor();

這裡我解釋一下下面這兩個方法的區別。

bool hasIndexedIsa();
bool isTaggedPointer();

本質上這兩個函式都是來判斷某個指標是否啟用了tagged pointer。不同的是
bool hasIndexedIsa();是用來判斷當前物件的isa是否啟用tagged pointer,而bool isTaggedPointer();函式用來判斷當前的物件指標是否啟用了tagged pointer。根據我的調研,比如NSNumberNSDate等值佔用記憶體比較少的物件啟用了tagged pointer

接下來是一系列管理引用計數以及生命週期的函式:

// Optimized calls to retain/release methods
    id retain();
    void release();
    id autorelease();
    // Implementations of retain/release methods
    id rootRetain();
    bool rootRelease();
    id rootAutorelease();
    bool rootTryRetain();
    bool rootReleaseShouldDealloc();
    uintptr_t rootRetainCount();

    // Implementation of dealloc methods
    bool rootIsDeallocating();
    void clearDeallocating();
    void rootDealloc();

是不是很熟悉呢?先看一下id retain()的內部實現:

inline id 
objc_object::retain()
{
    // UseGC is allowed here, but requires hasCustomRR.
    assert(!UseGC  ||  ISA()->hasCustomRR());
    assert(!isTaggedPointer());

    if (! ISA()->hasCustomRR()) {
        return rootRetain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}

翻譯一下,如果使用GC但是並沒有custom的retain/release方法則會直接斷言掉,如果支援tagged pointer就會直接斷言掉。接下來的流程就是如果沒有custom的retain/release方法就會呼叫rootRetain()。兜兜轉轉最後會呼叫如下方法:


ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    assert(!UseGC);
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (!newisa.indexed) goto unindexed;
        // don`t check newisa.fast_rr; we already called any RR overrides
        if (tryRetain && newisa.deallocating) goto tryfail;
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (carry) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) return rootRetain_overflow(tryRetain);
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits));

    if (transcribeToSideTable) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (!tryRetain && sideTableLocked) sidetable_unlock();
    return (id)this;

 tryfail:
    if (!tryRetain && sideTableLocked) sidetable_unlock();
    return nil;

 unindexed:
    if (!tryRetain && sideTableLocked) sidetable_unlock();
    if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
    else return sidetable_retain();
}

這段程式碼確實值得仔細研讀,轉換成虛擬碼的形式為:

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
   if (not support tagged pointer) return this;
   do {
       if (isa not support indexed) 
           sidetable_tryRetain(); //利用sidetable進行管理物件的引用計數。 
       if (isa support indexed)
           newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc+
   }
}

當然內部還有一些對於retry以及overflow的處理,這篇文章就不做過多的分析。但是大體上對於沒有進行isaindexed優化的物件的引用計數是依賴於SideTable()管理,而進行indexed優化的物件則直接利用isa指標進行管理。

接下來的private函式我就不多做分析了,值得注意的是,裡面有很多有關於sidetable的函式,但是這些函式是在NSObject.mm中實現的。

這樣有關於objc_obejct這個結構體的分析就到此為止。接下來還有一系列的文章進行分析。

相關文章