iOS底层探索之类的结构(中)

类的结构探索分析.png

在上一篇博客里面iOS底层探索之类的结构(上)已经大致的了解了类的结构

类的结构

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

class_data_bits

我们主要探究是class_data_bits_t bits,在bits里面有我们关心的类的信息。
那么我们怎么拿呢?先看看下面这个JPPerson

@interface JPPerson : NSObject
{
    int age;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;

- (void)sayHello;
+ (void)sayNB;
@end
@implementation JPPerson

- (instancetype)init{
     if (self = [super init]) {
          self.name = @"reno";
     }
     return self;
}
- (void)sayHello{
}
+ (void)sayNB{
}
@end

使用lldb调试 x/4gx 命令打印了JPPerson这个类的内存信息

调试结果

第一个是isa;第二个是superclasspo 出来是NSObjectJPPerson 是继承NSObject的;那么以此类推,第三个是cache,第四个是bits

bits

我们要了解bits里面的data信息,该怎么拿呢?光知道一个地址也不行啊?那么我们既然知道了isa(首地址),是不是就可以通过指针偏移内存偏移拿到呢?那么要偏移多少个呢?

类的结构

要想拿到bits,指针在内存中必须要平移指向bits,我知道isa是8个字节长度,superclass也是8个字节长度,那么cache_t呢?看看内部结构分析下

cache_t

cache_t内存大小

分析得到cache_t是16,那么加isasuperclass一共就是32个字节的长度。

(lldb) x/4gx JPPerson.class
0x100008358: 0x0000000100008380 0x000000010036a140
0x100008368: 0x00000001003623c0 0x0000802800000000
(lldb) p/x 0x100008358+0x20
(long) $1 = 0x0000000100008378
(lldb) p (class_data_bits_t*)0x0000000100008378
(class_data_bits_t *) $2 = 0x0000000100008378
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101b06bc0
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000144
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

好尴尬啊!class_rw_t里面没有看到我们该看到的信息啊!

class_rw_t

class_rw_t结构

从没有错啊!打印的信息和在class_rw_t源码里面的结构是一样的啊?
那怎么没有呢?我们刚打印的是结构信息,里面的方法没有找到,我们继续去看看源码,看看有没有打印属性的方法。
class_rw_t方法

还真有方法,你说巧不巧啊!嘿嘿
嘿嘿

那么靓仔,直接调用properties()不就可以了吗!

properties

(lldb) p $3.properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008198
      }
      arrayAndFlag = 4295000472
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->properties()
(lldb) 

调用properties()方法得到property_array_t

property_array_t

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};

list_array_tt

list_array_tt<property_t, property_list_t, RawPtr>

list_array_tt

知道了结构,我们就一层一层的往下扒

(lldb) p $3.properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008198
      }
      arrayAndFlag = 4295000472
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->properties()
(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
  ptr = 0x0000000100008198
}
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x0000000100008198
(lldb) p $7*
error: <user expression 9>:2:1: expected expression
;
^
(lldb) p *$7
(property_list_t) $8 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $8.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $8.get(1)
(property_t) $10 = (name = "hobby", attributes = "T@\"NSString\",C,N,V_hobby")
(lldb) 

还有谁?靓仔看到没有,JPPerson的属性打印出来了,我们要的类的信息成功输出来了!我这该死的魅力啊!

在这里插入图片描述

methods()

上面打印了属性,接下来该看看方法了

(lldb) p $3.methods()
(const method_array_t) $15 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008098
      }
      arrayAndFlag = 4295000216
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->methods()
(lldb) p $15.list.ptr
(method_list_t *const) $18 = 0x0000000100008098
(lldb) p *$18
(method_list_t) $19 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $19.get(0)
(method_t) $20 = {}
(lldb) p $19.get(0)
(method_t) $21 = {}
(lldb) p $19.get(1)
(method_t) $22 = {}

what ??什么鬼啊?怎么get打印不出来啊?别急继续探索下去

method_t

method_t

在源码里面发现了method_t结构体,里面嵌套了一个big结构体,big里面有个SEL 还有imp
big

那么可以通过method_t结构体拿到big,就可以获取到里面的方法,源码里面也发现了获取big方法big()

big()

(lldb) p $19.get(0).big
(method_t::big) $23 = {
  name = "sayHello"
  types = 0x0000000100003f94 "v16@0:8"
  imp = 0x0000000100003cd0 (JPObjcBuild`-[JPPerson sayHello])
}
  Fix-it applied, fixed expression was: 
    $19.get(0).big()
(lldb) p $19.get(1).big
(method_t::big) $24 = {
  name = "hobby"
  types = 0x0000000100003f8c "@16@0:8"
  imp = 0x0000000100003d40 (JPObjcBuild`-[JPPerson hobby])
}
  Fix-it applied, fixed expression was: 
    $19.get(1).big()
(lldb) p $19.get(2).big
(method_t::big) $25 = {
  name = "setHobby:"
  types = 0x0000000100003f9c "v24@0:8@16"
  imp = 0x0000000100003d70 (JPObjcBuild`-[JPPerson setHobby:])
}
  Fix-it applied, fixed expression was: 
    $19.get(2).big()
(lldb) p $19.get(3).big
(method_t::big) $26 = {
  name = "init"
  types = 0x0000000100003f8c "@16@0:8"
  imp = 0x0000000100003c70 (JPObjcBuild`-[JPPerson init])
}
  Fix-it applied, fixed expression was: 
    $19.get(3).big()
(lldb) p $19.get(4).big
(method_t::big) $27 = {
  name = "name"
  types = 0x0000000100003f8c "@16@0:8"
  imp = 0x0000000100003ce0 (JPObjcBuild`-[JPPerson name])
}
  Fix-it applied, fixed expression was: 
    $19.get(4).big()
(lldb) p $19.get(5).big
(method_t::big) $28 = {
  name = "setName:"
  types = 0x0000000100003f9c "v24@0:8@16"
  imp = 0x0000000100003d10 (JPObjcBuild`-[JPPerson setName:])
}
  Fix-it applied, fixed expression was: 
    $19.get(5).big()

干的漂亮!哈哈😁,方法列表里面count值为6,一共有6个都打印了出来了,包括setter方法和getter方法,靓仔就问你服不服!

服不服

那么有的靓仔肯定不服了,我怎么没有看到方法(+方法)和成员变量打印出来呢?你在这里装什么大一瓣蒜啊!

好,不服是吧!那么我们接着往下探索。

ivars

属性和成员变量在内存中存放的位置是不一样的,在WWDC2020里面介绍了Clean MemoryDirty Memory

Clean Memory

clean memory 加载后不会发生改变的内存class_ro_t 就属于clean memory,因为它是只读的不会,不会对齐内存进行修改clean memory
是可以进行移除的,从而节省更多的内存空间,因为如果你有需要clean memory,系统可以从磁盘中重新加载

Dirty Memory

dirty memory 是指在进程运行时会发生改变的内存 类结构一经使用就会变成 dirty memory,因为运行时会向它写入新的数据。例如创建一个新的方法缓存并从类中指向它,初始化类相关的子类和父类dirty memory是类数据被分成两部分的主要原因

dirty memory要比clean memory昂贵的多,只要程序运行它就必须一直存在,通过分离出那些不会被改变的数据,可以把大部分的类数据存储为clean memory

请看下面这个图(WWDC2020视频里面截取的)

class_ro_t

从图中我们知道成员变量在class_ro_t里面,那么我们打印一下看看

ro

既然里面有ivars成员信息,那么再打印出来看看

(lldb) p $6.ivars
(const ivar_list_t *const) $7 = 0x0000000100008130
(lldb) p *$7
(const ivar_list_t) $8 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $7.get(0)
(ivar_t) $9 = {
  offset = 0x0000000100008340
  name = 0x0000000100003f2a "age"
  type = 0x0000000100003f7e "i"
  alignment_raw = 2
  size = 4
}
  Fix-it applied, fixed expression was: 
    $7->get(0)
(lldb) p $7.get(1)
(ivar_t) $10 = {
  offset = 0x0000000100008348
  name = 0x0000000100003f2e "_name"
  type = 0x0000000100003f80 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  Fix-it applied, fixed expression was: 
    $7->get(1)
(lldb) p $7.get(2)
(ivar_t) $11 = {
  offset = 0x0000000100008350
  name = 0x0000000100003f34 "_hobby"
  type = 0x0000000100003f80 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  Fix-it applied, fixed expression was: 
    $7->get(2)
(lldb) 

成员信息的数据类型,名称、内存大小,都打印出来了。

类方法

在上面的测试中,JPPerson的对象方法可以正常打印出来,是在JPPerson.class中获取打印的。在MachOView中,也是可以看到,类方法确实存在的。

在这里插入图片描述

那么类方法也就是加号方法,是否是在元类中的呢?对象方法在类中,类方法在元类中,这不是很符合逻辑吗?好,那就去探索验证一下

(lldb) x/4gx JPPerson.class
0x100008358: 0x0000000100008380 0x000000010036a140
0x100008368: 0x00000001003623c0 0x0000802800000000
(lldb) p/x 0x0000000100008380 + 0x20
(long) $19 = 0x00000001000083a0
(lldb) p/x (class_data_bits_t*)0x00000001000083a0
(class_data_bits_t *) $20 = 0x00000001000083a0
(lldb) p $20->data()
(class_rw_t *) $21 = 0x0000000101337080
(lldb) p *$21
(class_rw_t) $22 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4315117329
    }
  }
  firstSubclass = 0x00000001000083a8
  nextSiblingClass = 0x00007fff80111eb0
}
(lldb) p $22.methods()
(const method_array_t) $23 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008208
      }
      arrayAndFlag = 4295000584
    }
  }
}
(lldb) p $23.list.ptr
(method_list_t *const) $24 = 0x0000000100008208
(lldb) p *$24
(method_list_t) $25 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $25.get(0).big()
(method_t::big) $26 = {
  name = "sayNB"
  types = 0x0000000100003f94 "v16@0:8"
  imp = 0x0000000100003da0 (JPObjcBuild`+[JPPerson sayNB])
}
(lldb) 

哈哈,还有谁


还有谁?

这波操作,就问你服不服!

总结

  • 元类isa指向:元类isa->根元类isa->根元类(NSObject的元类)
  • 元类继承关系:类继承isa->根元类isa->NSObject->nil
  • 类中有isa、superclass、chche、bits 成员变量,
  • bits 存储着属性列表、方法列表、成员变量列表、协议列表等

更多内容持续更新

iOS底层探索之类的结构(下)

🌹 请动动你的小手,点个赞👍🌹

🌹 喜欢的可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我,哈哈😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

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

推荐阅读更多精彩内容