OC類的結(jié)構(gòu) objc_class

objc_class 靜態(tài)結(jié)構(gòu)分析

struct objc_class : objc_object {
    // Class ISA; 
    Class superclass; 
    cache_t cache; 
    class_data_bits_t bits;  
}

今天看看 objc_class 中到底有哪些東西,去掉了結(jié)構(gòu)體函數(shù)也就幾個(gè)成員

  • isaobjc_object中繼承而來
  • superclass 存放的是父類的指針
  • cache 存放著一些使用過的方法緩存
  • bits 存的是class_rw_t的指針,還有rr/alloc的一個(gè)標(biāo)記,和isa有點(diǎn)類似

類數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)

//在 class_data_bits_t 中主要的就是 class_rw_t 
struct class_rw_t {
    //這注釋什么意思, 難道是說這些都會(huì)體現(xiàn)在符號(hào)表里? 這個(gè)之后再探索
    // 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;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
}
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;
    }
};

猜測(cè):

  1. 這兩個(gè)結(jié)構(gòu)記錄著 屬性、方法、成員變量
  2. class_rw_t 應(yīng)該是 readwrite,用來存儲(chǔ)擴(kuò)展 協(xié)議、方法、成員變量,并含有只讀的class_ro_t ro
  3. class_ro_t 應(yīng)該是 readonly,經(jīng)過一旦編譯就不會(huì)改變

跑起來驗(yàn)證我的猜想

//定義的類結(jié)構(gòu)
@interface LGPerson : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

- (void)sayHello;
+ (void)sayHappy;
@end

//LGPerson *person = [LGPerson new];
  1. 準(zhǔn)備工作 找到類對(duì)象
//查看person對(duì)象內(nèi)存
(lldb) x/2gx person
0x101e07f90: 0x001d8001000023b5 0x0000000000000000

//拿到 isa,換算成類對(duì)象地址  #   define ISA_MASK        0x00007ffffffffff8ULL
(lldb) p/x 0x001d8001000023b5 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000023b0
(lldb) po 0x00000001000023b0
LGPerson

  1. 類對(duì)象內(nèi)存數(shù)據(jù)
//LGPerson 0x00000001000023b0
//查看 LGPerson類對(duì)象內(nèi)存數(shù)據(jù)
(lldb) x/2gx 0x00000001000023b0
0x1000023b0: 0x001d800100002389 0x0000000100b37140
  1. 確實(shí)數(shù)據(jù)具體的位置
objc_class 各中數(shù)據(jù)的大小
struct objc_class : objc_object {
    Class  isa;  // size 8 ,pointer
    Class superclass;  // size 8 ,pointer
    cache_t cache;  // size 16 , struct
    class_data_bits_t bits;  // size 8 , struct
}

typedef uint32_t mask_t; // x86_64 & arm64
struct cache_t {
    struct bucket_t *_buckets;  // size 8
    mask_t _mask;  // size 4
    mask_t _occupied; // size 4
}

struct class_data_bits_t {
    uintptr_t bits; // size 8
}

匯總一下

名稱 isa superclass cache bits
類型 Class Class cache_t class_data_bits_t
大小 8 8 16 8

其中 cache_tbits 的是結(jié)構(gòu)體不是指針,struct大小根據(jù)成員的大小而變換。

那么偏移量的計(jì)算就很簡(jiǎn)單了

  1. 偏移到 bits 查看
  • 那么只要在類對(duì)象地址基礎(chǔ)上偏移32就是bits的地址,驗(yàn)證一下
//LGPerson 0x00000001000023b0
//偏移32 查看bits 0x00000001000023b0 + 0d32 = 0x00000001000023d0
(lldb) x/2gx 0x00000001000023d0
0x1000023d0: 0x0000000101e062f4 0x0000000000000000

//0x00000001020012f4 就是bits的地址
//想拿到 class_rw_t 需要經(jīng)過MASK
// #define FAST_DATA_MASK          0x00007ffffffffff8UL
(lldb) p/x 0x0000000101e062f4 & 0x00007ffffffffff8UL
(unsigned long) $6 = 0x0000000101e062f0
//將計(jì)算你結(jié)果 強(qiáng)轉(zhuǎn) class_rw_t *
(lldb) p (class_rw_t *)0x0000000101e062f0
(class_rw_t *) $7 = 0x0000000101e062f0


當(dāng)然也可以用 objc_object 中的函數(shù) data() 拿到 class_rw_t *,效果是一樣的

(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $5 = 0x00000001000023d0
// 調(diào)用class_data_bits_t 中的data()函數(shù)
(lldb) p $5->data()
(class_rw_t *) $10 = 0x0000000101e062f0
  1. 查看 class_rw_t 中的數(shù)據(jù)
(class_rw_t *) $10 = 0x0000000101e062f0
(lldb) p *$5
(class_rw_t) $12 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002308
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002240
        arrayAndFlag = 4294976064
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x00000001000022f0
        arrayAndFlag = 4294976240
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
  demangledName = 0x0000000100001f80 "LGPerson"
}
  1. class_rw_t.properties
//打印 class_rw_t 的 properties
(lldb) p $12.properties
(property_array_t) $13 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x00000001000022f0
      arrayAndFlag = 4294976240
    }
  }
}
(lldb) p $13.list
(property_list_t *) $14 = 0x00000001000022f0
(lldb) p $14.first
(property_t) $15 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
  Fix-it applied, fixed expression was: 
    $14->first

最終在 properties 中找到了 定義的屬性 nickName

  1. class_rw_t.methods
(lldb) p $12.methods
(method_array_t) $17 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002240
      arrayAndFlag = 4294976064
    }
  }
}
(lldb) p $17.list
(method_list_t *) $18 = 0x0000000100002240
(lldb) p $18->get(0)
(method_t) $22 = {
  name = "sayHello"
  types = 0x0000000100001f8b "v16@0:8"
  imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
(lldb) p $18->get(1)
(method_t) $23 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f8b "v16@0:8"
  imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $18->get(2)
(method_t) $24 = {
  name = "nickName"
  types = 0x0000000100001f93 "@16@0:8"
  imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
(lldb) p $18->get(3)
(method_t) $25 = {
  name = "setNickName:"
  types = 0x0000000100001f9b "v24@0:8@16"
  imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}

最終在 methods 中找到了

  • 自己定義的sayHello方法
  • 系統(tǒng)生成的c++ 析構(gòu)方法nickName
  • 系統(tǒng)生成的get方法.cxx_destruct
  • 系統(tǒng)生成的set方法 setNickName:
  1. 查看 class_ro_t 中的數(shù)據(jù)
(lldb) p *$12.ro
(const class_ro_t) $29 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001f89 "\x02"
  name = 0x0000000100001f80 "LGPerson"
  baseMethodList = 0x0000000100002240
  baseProtocols = 0x0000000000000000
  ivars = 0x00000001000022a8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000022f0
}
  • 這里有個(gè) const 說明class_ro_t是一個(gè)常量。
  • 這里存放這類名 等信息
  1. class_ro_t.ivars
(lldb) p *$29.ivars
(const ivar_list_t) $31 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x0000000100002378
      name = 0x0000000100001e64 "hobby"
      type = 0x0000000100001fa6 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $31.get(0)
(ivar_t) $33 = {
  offset = 0x0000000100002378
  name = 0x0000000100001e64 "hobby"
  type = 0x0000000100001fa6 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $31.get(1)
(ivar_t) $34 = {
  offset = 0x0000000100002380
  name = 0x0000000100001e6a "_nickName"
  type = 0x0000000100001fa6 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) 

這里找到了兩個(gè)成員變量

  • 自定義的成員變量 hobby
  • 系統(tǒng)生成的成員變量 _nickName
  1. class_ro_t.baseMethodList
(lldb) p *$29.baseMethodList
(method_list_t) $36 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
  }
}
(lldb) p $36.get(0)
(method_t) $37 = {
  name = "sayHello"
  types = 0x0000000100001f8b "v16@0:8"
  imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
(lldb) p $36.get(1)
(method_t) $38 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f8b "v16@0:8"
  imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $36.get(2)
(method_t) $39 = {
  name = "nickName"
  types = 0x0000000100001f93 "@16@0:8"
  imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
(lldb) p $36.get(3)
(method_t) $40 = {
  name = "setNickName:"
  types = 0x0000000100001f9b "v24@0:8@16"
  imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}

這里居然和 class_rw_t中的一模一樣

class_rw_t 是可以在運(yùn)行時(shí)來拓展類的一些屬性、方法和協(xié)議等內(nèi)容。
class_ro_t 是在編譯時(shí)就已經(jīng)確定了的,存儲(chǔ)的是類的成員變量、屬性、方法和協(xié)議等內(nèi)容。
運(yùn)行時(shí),會(huì)將 ro 中的數(shù)據(jù) copy 到 rw 中。

  1. 類方法在哪里?

我在 LGPerson中定義了 + (void)sayHappy;類方法但是好像并沒有找到,不出意外的話應(yīng)該在元類對(duì)象中。

// 0x00000001000023b0 LGPerson類對(duì)象
(lldb) x/4gx 0x00000001000023b0
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da260 0x0000000000000000
//拿到LGPerson 元類對(duì)象
(lldb) p/x 0x001d800100002389 & 0x00007ffffffffff8ULL
(unsigned long long) $41 = 0x0000000100002388
//打印元類內(nèi)存數(shù)據(jù)
(lldb) x/6gx 0x0000000100002388
0x100002388: 0x001d800100b370f1 0x0000000100b370f0
0x100002398: 0x0000000100f42780 0x0000000300000007
0x1000023a8: 0x0000000101e062b0 0x001d800100002389
//拿到 元類的 class_rw_t *
(lldb) p (class_rw_t *)0x1000023a8
(class_rw_t *) $43 = 0x00000001000023a8
(lldb) p *$43
(class_rw_t) $44
p $44.ro
(const class_ro_t *) $45 = 0x001d800100002389
(lldb) p *$45
//error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
 //最后發(fā)現(xiàn) 元類的 ro 好像不讓訪問

換一種方式證明

  const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); 

// 輸出 0x1000021e0-0x1000021e0

這結(jié)果太尷尬了都能找到,看了一下 class_getClassMethod的源碼找到的問題所在

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}

Class getMeta() {
    //如果是元類就返回 this,不是元類就返回 isa
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

class_getClassMethod 找元類中找到了sayHappy的實(shí)例方法,所以可以得出類方法是保存在元類中。

class_ro_t 和 class_rw_t 的關(guān)系

class_ro_t由編譯階段確定的只讀數(shù)據(jù)
class_rw_t是在運(yùn)行時(shí)創(chuàng)建

在什么時(shí)候創(chuàng)建 rw
  • realizeClass中對(duì)rw進(jìn)行了第一次初始化
  • 在方法慢速查找的時(shí)候,如果類對(duì)象未實(shí)現(xiàn)isRealized就會(huì)觸發(fā)realizeClass
realizeClass 調(diào)用棧
>objc_alloc
  > callAlloc
    > (匯編)_objc_msgSend_uncached
      > lookUpImpOrForward
        > realizeClass
realizeClass 中的rw和ro
static Class realizeClass(Class cls)
{
    ......
    
    //初次讀取cls->data() 指向的是 class_ro_t
    //之后的讀取cls->data() 指向的是 class_rw_t
    //兩種結(jié)構(gòu)體 class_ro_t 和 class_rw_t 第一個(gè)成員都是flags,強(qiáng)轉(zhuǎn)至少保證了flag的正常獲取
    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) { //判斷是否是rw
       //如果rw已經(jīng)分配了內(nèi)存,則rw指向cls->data(),然后將rw的ro指針指向之前最開始的ro
        rw = cls->data();
        ro = cls->data()->ro;
        //修改 rw的flags
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        //如果rw并不存在,則為rw分配空間
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        //將rw->ro設(shè)置為原始的ro
        rw->ro = ro;
        //設(shè)置 rw的flags
        rw->flags = RW_REALIZED|RW_REALIZING;
        //修改cls->data(),也就是讓cls->data()指向rw
        cls->setData(rw);
    }
    isMeta = ro->flags & RO_META;
    ......
}

總結(jié)

  • 實(shí)例方法存放在類對(duì)象中 ,類方法存放在元類對(duì)象中
  • 類的方法、協(xié)議、屬性都存放在類對(duì)象的class_rw_t中
  • class_ro_t 存放著編譯時(shí)確定的方法、協(xié)議、屬性、成員變量

遺留的問題

  • 為什么元類對(duì)象的 ro 不能訪問

方案一

realizeClass的時(shí)候看到了元類處理rorw的過程,直接拿其中的ro

// 用來判斷是否是元類的Mask #define RO_META               (1<<0)
(lldb) po ro->flags & (1<<0)
1
(lldb) p *ro
(const class_ro_t) $0 = {
  flags = 389
  instanceStart = 40
  instanceSize = 40
  reserved = 0
  ivarLayout = 0x0000000000000000
  name = 0x0000000100001f85 "LGPerson"
  baseMethodList = 0x00000001000021d8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000000000000
}
(lldb) p $0.baseMethodList
(method_list_t *const) $4 = 0x00000001000021d8
(lldb) p $4->get(0)
(method_t) $5 = {
  name = "sayHappy"
  types = 0x0000000100001f90 "v16@0:8"
  imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
}

方案二

好吧必須承認(rèn)的是我粗心了,元類偏移32的指針直接當(dāng)成class_rw_t *來處理,實(shí)際上是class_data_bits_t *

//LGPerson的元類  0x0000000100002390
(lldb) x/6gx 0x0000000100002390
0x100002390: 0x001d800100b370f1 0x0000000100b370f0
0x1000023a0: 0x000000010180a1a0 0x0000000400000007
0x1000023b0: 0x000000010192ee50 0x001d800100002391
(lldb) p (class_data_bits_t *)0x1000023b0
(class_data_bits_t *) $14 = 0x00000001000023b0
(lldb) p *$14
(class_data_bits_t) $15 = (bits = 4321373776)
(lldb) p $14->data()
(class_rw_t *) $17 = 0x000000010192ee50
(lldb) p $17->ro
(const class_ro_t *) $18 = 0x00000001000021f8
(lldb) p *$18
(const class_ro_t) $19 = {
  flags = 389
  instanceStart = 40
  instanceSize = 40
  reserved = 0
  ivarLayout = 0x0000000000000000
  name = 0x0000000100001f85 "LGPerson"
  baseMethodList = 0x00000001000021d8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000000000000
}
(lldb) p $19.baseMethodList
(method_list_t *const) $20 = 0x00000001000021d8
(lldb) p $20->get(0)
(method_t) $21 = {
  name = "sayHappy"
  types = 0x0000000100001f90 "v16@0:8"
  imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
}
//激動(dòng)啊 折騰了兩天,終于看到了美麗的sayHappy

收工

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

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

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