iOS Class實現(xiàn)原理-isa

本文會闡述下面幾個問題

1、isa是什么
2、isa的內(nèi)存布局
3、Class與isa背后的設(shè)計

查看源碼(源碼版本objc4-781.2)

源碼地址
打開objc-private.h查看源碼,發(fā)現(xiàn)isa是一個聯(lián)合體,聯(lián)合體各個成員變量之間共享內(nèi)存,所以isa占8個字節(jié)

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

扒開這個宏定義ISA_BITFIELD

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 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)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif

如上isa_t聯(lián)合體在arm64和x86的語義字段完全相同并且都占用8個字節(jié),但是內(nèi)存布局存在很大差異,本篇文章會以arm64為例展開介紹,所以isa_t長這樣,有三個成員變量cls,bits,和一個匿名結(jié)構(gòu)體,三者共享一塊內(nèi)存

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

    struct {
      uintptr_t nonpointer        : 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
    };
};
從Class的定義開始探究isa
struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    uintptr_t isaBits() const;

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);

    // 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 hasNonpointerIsa();
    bool isTaggedPointer();
    bool isBasicTaggedPointer();
    bool isExtTaggedPointer();
    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();
    ...
};

我們來詳細的看下里面的函數(shù)源碼,首先看下isa的初始化函數(shù),如下

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}

inline void 
objc_object::initClassIsa(Class cls)
{
    if (DisableNonpointerIsa  ||  cls->instancesRequireRawIsa()) {
        initIsa(cls, false/*not nonpointer*/, false);
    } else {
        initIsa(cls, true/*nonpointer*/, false);
    }
}

inline void
objc_object::initProtocolIsa(Class cls)
{
    return initClassIsa(cls);
}

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

    initIsa(cls, true, hasCxxDtor);
}

最終都會調(diào)用到objc_object::initIsa函數(shù),精簡定義如下,省略了斷言和條件編譯不生效的部分

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{     
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        isa_t newisa(0);
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;

        isa = newisa;
    }
}

我們來大概翻譯下這個函數(shù),nonpointer占一個二進制位,用來標(biāo)識內(nèi)存是否是64位系統(tǒng)的布局,if 0函數(shù)直接賦值為cls地址,else初始化一個isa_t類型的newisa聯(lián)合體,分別對bits、has_cxx_dtor、shiftcls進行賦值,然后賦值給isa,所以cls是32位系統(tǒng)的類指針,而64位系統(tǒng)是bits通過位運算來獲取類指針的

isa的get函數(shù)與初始化函數(shù)對應(yīng),不再贅述,摘出幾處重點說明,Apple的注釋還是那么的清晰

// 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();

hasAssociatedObjects可能有關(guān)聯(lián)對象,isWeaklyReferenced是否有弱引用,hasCxxDtor是否有c++析構(gòu)函數(shù)的實現(xiàn)

我們還是看下源碼,如下isa.nonpointer在64位系統(tǒng)是1,函數(shù)的返回值就是isa對應(yīng)的成員

inline bool
objc_object::hasAssociatedObjects()
{
    if (isTaggedPointer()) return true;
    if (isa.nonpointer) return isa.has_assoc;
    return true;
}

inline bool
objc_object::isWeaklyReferenced()
{
    ASSERT(!isTaggedPointer());
    if (isa.nonpointer) return isa.weakly_referenced;
    else return sidetable_isWeaklyReferenced();
}

inline bool
objc_object::hasCxxDtor()
{
    ASSERT(!isTaggedPointer());
    if (isa.nonpointer) return isa.has_cxx_dtor;
    else return isa.cls->hasCxxDtor();
}
isa的內(nèi)存布局

基于此,我們得出以下結(jié)論,重點看下上面的匿名結(jié)構(gòu)體,有9個成員變量,用位域來標(biāo)識

  • nonpointer
    占一個二進制位,用來標(biāo)識內(nèi)存是否是64位系統(tǒng)的布局,0:純isa指針,1:不?是類對象地址,isa 中包含了類信息、對象的引?計數(shù)等
  • has_assoc
    占用一個二進制位,標(biāo)識對象含有或者曾經(jīng)含有關(guān)聯(lián)對象
  • has_cxx_dtor
    這一位表示當(dāng)前對象是否有c++析構(gòu)函數(shù)
  • shiftcls
    占33位,用于存儲類地址
  • magic
    占6位,標(biāo)識當(dāng)前對象是否已經(jīng)初始化
  • weakly_referenced
    占1位,標(biāo)識當(dāng)前對象是否有弱引用
  • deallocating
    占1位,標(biāo)識當(dāng)前對象是否正在釋放
  • has_sidetable_rc 和 extra_rc
    分別占1位和19位用于存儲優(yōu)化引用計數(shù)
Class與isa背后的設(shè)計

這里有一篇文章寫得很好
放一張經(jīng)典圖片

class_isa.png

有關(guān)這張圖,網(wǎng)上解釋的很清楚了,這里想討論三個問題

  • Apple為什么要設(shè)計meta class這個東西?
    假設(shè)沒有meta class這個東西,我們知道實例對象只存儲了成員變量的值和一個isa指針,對象調(diào)用方法是通過isa指針找到類,類對象存儲了方法列表等一系列通用結(jié)構(gòu),從而實現(xiàn)了方法繼承、緩存、調(diào)用等操作,這極大的節(jié)約了內(nèi)存,試問如果每個實例對象都存儲一遍方法列表,這將是多大的開銷,然而類方法放在哪里呢,我們知道OC方法調(diào)用是通過選擇器sel找到最終的imp函數(shù)指針,從而完成調(diào)用過程,那sel是什么呢,就是一個編碼過的字符串,如果有同名的類方法和實例方法怎么辦,sel相同,我們知道,這是無法通過編譯的,因為編譯器要做方法唯一性校驗,所以我們需要另外一個結(jié)構(gòu)來存儲類方法,meta class應(yīng)運而生

  • 為什么NSObject(根類)的元類的isa指針指向的是根元類自己?
    這個問題可以反過來想,如果根元類的isa指向nil會有什么問題?
    isa指針指向的是自己的抽象工廠對象,自己若存在,則一級一級往上回溯,一定要是個閉環(huán),試問如果訪問到某一層的時候發(fā)現(xiàn)是個nil,那么自己又是怎么存在的呢

  • 為什么根元類的superclass指針指向NSObject,而NSObject的superclass指向的卻是nil?
    NSObject已經(jīng)是根類了,無從繼承了,所以NSObject的superclass指向nil,NSObject是被設(shè)計出來的根類,meta class作為類自然要從NSObject派生出來,以具備OC類的結(jié)構(gòu)與各種特性

最后

本篇討論了Class的isa指針,下篇打算討論下Class的方法查詢、轉(zhuǎn)發(fā)、緩存等機制

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

相關(guān)閱讀更多精彩內(nèi)容

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