iOS底层探索之类的结构(中)
在上一篇博客里面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
;第二个是superclass
,po
出来是NSObject
,JPPerson
是继承NSObject
的;那么以此类推,第三个是cache
,第四个是bits
。
bits
我们要了解bits
里面的data
信息,该怎么拿呢?光知道一个地址也不行啊?那么我们既然知道了isa
(首地址),是不是就可以通过指针偏移
,内存偏移
拿到呢?那么要偏移多少个呢?
要想拿到
bits
,指针在内存中必须要平移指向bits
,我知道isa
是8个字节长度,superclass
也是8个字节长度,那么cache_t呢?看看内部结构分析下
cache_t
cache_t内存大小分析得到
cache_t
是16,那么加isa
和superclass
一共就是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
list_array_tt
<property_t, property_list_t, RawPtr>
知道了结构,我们就一层一层的往下扒
(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 Memory
和Dirty 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
视频里面截取的)
ro从图中我们知道成员变量在
class_ro_t
里面,那么我们打印一下看看
既然里面有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 存储着属性列表、方法列表、成员变量列表、协议列表等
更多内容持续更新
🌹 请动动你的小手,点个赞👍🌹
🌹 喜欢的可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我,哈哈😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹