OC中Class怎麼儲存方法

不會騎名字發表於2018-06-11

在上一篇初識isa文章中,我們提到物件的方法是儲存在類中,類方法是儲存在元類中

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;//0x18
    cache_t cache; //0x20            // formerly cache pointer and vtable
    class_data_bits_t bits;         // class_rw_t * plus custom rr/alloc flags
}
複製程式碼
  • isa(繼承自objc_object)指向元類
  • superclass指向父類
  • cache方法快取,當呼叫一次方法後就會快取進vtable中,加速下次呼叫
  • bits這是今天的主角,就是儲存類的方法、屬性和遵循的協議等資訊的地方(rr/alloc flags是什麼意思?哪位大神麻煩幫解釋下)

class_data_bits_t結構體

struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;
}
複製程式碼

這麼一看,結構體裡面只有一個bits?在objc_class結構體中的註釋寫到 class_data_bits_t 相當於 class_rw_t 指標加上 rr/alloc 的標誌(再次求教各位大神rr/alloc是什麼意思)。它為我們提供了簡單的方法返回class_rw_t *指標

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

FAST_DATA_MASK轉成二進位制是

0000 0000 0000 0000 0111 1111 1111 1111
1111 1111 1111 1111 1111 1111 1111 1000
複製程式碼

可以看出class_rw_t *位置在[3,47]

    bool getBit(uintptr_t bit)
    {
        return bits & bit;
    }
    bool isSwift() {
        return getBit(FAST_IS_SWIFT);
    }
    bool hasDefaultRR() {
        return getBit(FAST_HAS_DEFAULT_RR);
    }
    bool instancesRequireRawIsa() {
        return getBit(FAST_REQUIRES_RAW_ISA);
    }

複製程式碼
#define FAST_IS_SWIFT           (1UL<<0)
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR     (1UL<<1)
/*class's instances requires raw isa*/
#define FAST_REQUIRES_RAW_ISA   (1UL<<2)
複製程式碼

可以看出後三位標識

  • isSwift()

FAST_IS_SWIFT第一位,標識是不是swift類

  • hasDefaultRR()

FAST_HAS_DEFAULT_RR第二位,判斷當前類或者父類含有預設的 retain/release/autorelease/retainCount/ _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference方法

  • instancesRequireRawIsa()

FAST_REQUIRES_RAW_ISA第三位,當前類是否使用純指標表示

執行class_data_bits_t中的data()方法,或者objc_class中的data()方法都會返回一個class_rw_t*指標,因為,objc_class中的方法就是對class_data_bits_t的封裝

    class_data_bits_t bits; 
    class_rw_t *data() { 
        return bits.data();
    }
複製程式碼

class_rw_t 與 class_ro_t

OC中所用到的方法,協議等都儲存在class_rw_t

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}
複製程式碼

其中還含有一個const class_ro_t *ro常量,存放了編譯就確定了的屬性/成員變數/方法等(屬性/成員變數應該不止編譯期間的,還有使用runtime新增的屬性,成員變數都是在這裡面檢視了下class_addIvar([XXObject class], "yty_test", sizeof(int), 0, "i");在原始碼中的確會更改ro,但是對我們生成的類沒有效果,只有我們使用runtime建立的類才有效果)

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
複製程式碼

在編譯期間類結構體中class_data_bits_t * bitsdata()中存放的是class_ro_t指標,在objc原始碼中可以找到static Class realizeClass(Class cls)方法

static Class realizeClass(Class cls)
{
    runtimeLock.assertWriting();

    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    ro = (const class_ro_t *) cls->data();//編譯期間存放的是class_ro_t
  if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        rw->ro = ro;
        rw->flags = RW_REALIZED|RW_REALIZING;
        cls->setData(rw);
    }
}
複製程式碼

該方法太長,只擷取了一部分

這個方法很簡單

  • class_data_bits_t 呼叫 data 方法,將結果從 class_rw_t 強制轉換為 class_ro_t 指標
  • 初始化一個 class_rw_t 結構體
  • 設定結構體 ro 的值以及 flag
  • 最後設定正確的 data

這個方法呼叫後,類中就有了class_rw_t了,但是這時候所有的方法,屬性,協議都還是空,這時候就會呼叫static void methodizeClass(Class cls)方法將自己本身存在的,分類中存在的都載入進class_rw_t

想要驗證編譯期間data()class_ro_t,可以參考深入解析 ObjC 中方法的結構

realizeClass

該方法是在類第一次初始化的時候分配可讀寫資料空間並返回真正的類結構,在返回出去的的類結構中包含了,所有自身,分類中的方法,屬性,協議

方法的結構

方法也是個結構體

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
}
複製程式碼
  • name 標識方法名字
  • types 方法型別(eg: [XXObject hello]的方法型別就是 v16@0:8)
  • imp 函式指標,真正呼叫的就是這個

最後讓我引用Draveness大神的總結

  • 類在記憶體中的位置是在編譯期間決定的,在之後修改程式碼,也不會改變記憶體中的位置。
  • 類的方法、屬性以及協議在編譯期間存放到了“錯誤”的位置,直到 realizeClass 執行之後,才放到了 class_rw_t 指向的只讀區域 class_ro_t,這樣我們即可以在執行時為 class_rw_t 新增方法,也不會影響類的只讀結構。
  • class_ro_t 中的屬性在執行期間就不能改變了,再新增方法時,會修改 class_rw_t 中的 methods 列表,而不是 class_ro_t 中的 baseMethods,對於方法的新增會在之後的文章中分析。

文章參考:
深入解析 ObjC 中方法的結構

相關文章