OC类的结构 objc_class

objc_class 静态结构分析

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

今天看看 objc_class 中到底有哪些东西,去掉了结构体函数也就几个成员

  • isaobjc_object中继承而来
  • superclass 存放的是父类的指针
  • cache 存放着一些使用过的方法缓存
  • bits 存的是class_rw_t的指针,还有rr/alloc的一个标记,和isa有点类似

类数据存储结构

//在 class_data_bits_t 中主要的就是 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;

#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;
    }
};

猜测:

  1. 这两个结构记录着 属性、方法、成员变量
  2. class_rw_t 应该是 readwrite,用来存储扩展 协议、方法、成员变量,并含有只读的class_ro_t ro
  3. class_ro_t 应该是 readonly,经过一旦编译就不会改变

跑起来验证我的猜想

//定义的类结构
@interface LGPerson : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

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

//LGPerson *person = [LGPerson new];
  1. 准备工作 找到类对象
//查看person对象内存
(lldb) x/2gx person
0x101e07f90: 0x001d8001000023b5 0x0000000000000000

//拿到 isa,换算成类对象地址  #   define ISA_MASK        0x00007ffffffffff8ULL
(lldb) p/x 0x001d8001000023b5 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000023b0
(lldb) po 0x00000001000023b0
LGPerson

  1. 类对象内存数据
//LGPerson 0x00000001000023b0
//查看 LGPerson类对象内存数据
(lldb) x/2gx 0x00000001000023b0
0x1000023b0: 0x001d800100002389 0x0000000100b37140
  1. 确实数据具体的位置
objc_class 各中数据的大小
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 的是结构体不是指针,struct大小根据成员的大小而变换。

那么偏移量的计算就很简单了

  1. 偏移到 bits 查看
  • 那么只要在类对象地址基础上偏移32就是bits的地址,验证一下
//LGPerson 0x00000001000023b0
//偏移32 查看bits 0x00000001000023b0 + 0d32 = 0x00000001000023d0
(lldb) x/2gx 0x00000001000023d0
0x1000023d0: 0x0000000101e062f4 0x0000000000000000

//0x00000001020012f4 就是bits的地址
//想拿到 class_rw_t 需要经过MASK
// #define FAST_DATA_MASK          0x00007ffffffffff8UL
(lldb) p/x 0x0000000101e062f4 & 0x00007ffffffffff8UL
(unsigned long) $6 = 0x0000000101e062f0
//将计算你结果 强转 class_rw_t *
(lldb) p (class_rw_t *)0x0000000101e062f0
(class_rw_t *) $7 = 0x0000000101e062f0


当然也可以用 objc_object 中的函数 data() 拿到 class_rw_t *,效果是一样的

(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $5 = 0x00000001000023d0
// 调用class_data_bits_t 中的data()函数
(lldb) p $5->data()
(class_rw_t *) $10 = 0x0000000101e062f0
  1. 查看 class_rw_t 中的数据
(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方法
  • 系统生成的c++ 析构方法nickName
  • 系统生成的get方法.cxx_destruct
  • 系统生成的set方法 setNickName:
  1. 查看 class_ro_t 中的数据
(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
}
  • 这里有个 const 说明class_ro_t是一个常量。
  • 这里存放这类名 等信息
  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) 

这里找到了两个成员变量

  • 自定义的成员变量 hobby
  • 系统生成的成员变量 _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 是可以在运行时来拓展类的一些属性、方法和协议等内容。
class_ro_t 是在编译时就已经确定了的,存储的是类的成员变量、属性、方法和协议等内容。
运行时,会将 ro 中的数据 copy 到 rw 中。

  1. 类方法在哪里?

我在 LGPerson中定义了 + (void)sayHappy;类方法但是好像并没有找到,不出意外的话应该在元类对象中。

// 0x00000001000023b0 LGPerson类对象
(lldb) x/4gx 0x00000001000023b0
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da260 0x0000000000000000
//拿到LGPerson 元类对象
(lldb) p/x 0x001d800100002389 & 0x00007ffffffffff8ULL
(unsigned long long) $41 = 0x0000000100002388
//打印元类内存数据
(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
 //最后发现 元类的 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

这结果太尴尬了都能找到,看了一下 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的实例方法,所以可以得出类方法是保存在元类中。

class_ro_t 和 class_rw_t 的关系

class_ro_t由编译阶段确定的只读数据
class_rw_t是在运行时创建

在什么时候创建 rw
  • realizeClass中对rw进行了第一次初始化
  • 在方法慢速查找的时候,如果类对象未实现isRealized就会触发realizeClass
realizeClass 调用栈
>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
    //两种结构体 class_ro_t 和 class_rw_t 第一个成员都是flags,强转至少保证了flag的正常获取
    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) { //判断是否是rw
       //如果rw已经分配了内存,则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设置为原始的ro
        rw->ro = ro;
        //设置 rw的flags
        rw->flags = RW_REALIZED|RW_REALIZING;
        //修改cls->data(),也就是让cls->data()指向rw
        cls->setData(rw);
    }
    isMeta = ro->flags & RO_META;
    ......
}

总结

  • 实例方法存放在类对象中 ,类方法存放在元类对象中
  • 类的方法、协议、属性都存放在类对象的class_rw_t中
  • class_ro_t 存放着编译时确定的方法、协议、属性、成员变量

遗留的问题

  • 为什么元类对象的 ro 不能访问

方案一

realizeClass的时候看到了元类处理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)
}

方案二

好吧必须承认的是我粗心了,元类偏移32的指针直接当成class_rw_t *来处理,实际上是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)
}
//激动啊 折腾了两天,终于看到了美丽的sayHappy

收工

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容