本页所使用的objc runtime 756.2,来自GITHUB
1.概念
1.1 类Class 的类型
Class在源码里的真正类型为objc_class的结构体。
先查看源码——在Xcode按下Shift+Command+O,选择objc-runtime-new.h,搜索objc_class,可以得知,Class底层编译实现的前4行如下:
可知类结构如下:
A. //Class ISA
继承自父类 objc_class 的 isa ,指向类的指针不需显示(内存占8位),这里它返回一个nonpointer的指针,相关源码如下:
/// Represents an instance of a class. struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; };
B. Class superclass;
指向本类的父类(内存占8位)
C. cache_t cache;
存储指针和vtable,加速方法调用(内存占8位)
D. class_data_bits_t bits;
存储类的方法、属性、遵循的协议等信息的地方(内存占8位)
E. 其他方法/函数 – 不占内存
为何cache_t cache 这个变量占位位16,而不是8?
原因是因为这里的cache_t是结构体,其占内存为根据内部属性结构而定,为防止溢出,给予16位字节,而不是指针所需的8位。
Talk is cheap, show me the code, 伸伸手指,点开cache_t 的内部结构
我们来看看属性:
- bucket_t, 结构体,占8位字节
- mask_t,点开后具体实现为如下:
typedef uint32_t mask_t;
为整型,占4个字节
- mask_t
typedef uint32_t mask_t;
也是整形,占4个字节
共计8+4+4 = 16个字节
1.2 objc_class继承
在底层实现里,objc_class 继承自objc_object 的结构体
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
1.3 关于NSobject
万物皆对象 - Class 继承自NSObject
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
2. 类的属性与成员变量
属性的存储
上面提到了类对象的结构原理,我们知道了类结构里,类型位class_data_bits_t 的bits下面我们来探寻一下,类里面的属性上怎么保存的。
我们在代码里生成一个类,分别有成员变量hobby,以及属性nickName
代码如下:
@interface Person : NSObject{ NSString *hobby; } @property (nonatomic, copy) NSString *nickName;
在主线代码引入:
int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *person = [LGPerson alloc]; Class pClass = object_getClass(person); NSLog(@"%@ -- %p", person, pClass); } return 0; }
然后打印得到的类pClass,通过x/4gx 获取类结构如下:
(lldb) x/4gx pClass 0x1000023d0: 0x001d8001000023a9 0x0000000100b37140 0x1000023e0: 0x00000001003da280 0x0000000000000000
观察结果得知0x1000023d0 位pClass在内存中的首地址,根据内存偏移原则,加上16+8+8共32位,即可得到bits的结构。
0x1000023d0,偏移32位,得到0x1000023f0。注意,这里要用p打印指针,而非打印值用的po
(lldb) p 0x1000023f0 4294976496
oh NO!得到的是具体的值,这里需要强转一下:
(lldb) p (class_data_bits_t *)0x1000023f0 (class_data_bits_t *) $3 = 0x00000001000023f0
得到$3 的值,并不是我们想要的结构,留意到objc_class里的方法 data() 方法:
struct objc_class : objc_object { // Class ISA; // 8 Class superclass; // 8 cache_t cache; // 16 不是8 // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags class_rw_t *data() { return bits.data(); }
data() 返回是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;
留意到里面有 methods、properties、protocols等,原来这里等值便是类的相关属性方法等等结构。
现在对$3 执行data() 方法:
(lldb) p $3->data() (class_rw_t *) $5 = 0x0000000000000000
对得到的$5 进行值打印:
(lldb) p *$5 (class_rw_t) $5 = { 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 = 0x0000000000000000 }
如上所示,$5 内结构一目了然,方法,属性,协议都显示了。
继续获取属性值:
(lldb) p $5.properties (property_array_t) $7 = { list_array_tt<property_t, property_list_t> = { = { list = 0x00000001000022f0 arrayAndFlag = 4294976240 } } }
得到一个二维数组的$7,观察属性列表的类型为property_array_t,继承自list_array_tt,内部拥有
(lldb) p $7.list (property_list_t *) $8 = 0x00000001000022f0
再次对$8 进行* 取值,得到如下结果:
(lldb) p *$8 (property_list_t) $9 = { entsize_list_tt<property_t, property_list_t, 0> = { entsizeAndFlags = 16 count = 1 first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName") } }
从结果可以看出,我们的属性,就保存在properties 里中。
成员变量的存储
上面分析了属性的存储,但是我们没有看到成员变量 hobby,我们在properties 属性里,没有看到它,到底在哪里呢?下面我们继续查找。
既然不在properties 里,那尝试一下在$5 的 ro 里进行取值
(lldb) p $5.ro (const class_ro_t *) $10 = 0x0000000100002308
得到class_ro_t 类型的结构体,继续对他取值
(lldb) p *$10 (const class_ro_t) $11 = { flags = 388 instanceStart = 8 instanceSize = 24 reserved = 0 ivarLayout = 0x0000000100001f8a "\x02" name = 0x0000000100001f81 "LGPerson" baseMethodList = 0x0000000100002240 baseProtocols = 0x0000000000000000 ivars = 0x00000001000022a8 weakIvarLayout = 0x0000000000000000 baseProperties = 0x00000001000022f0 }
值这里可以看到ivars,就是我们需要的变量列表,打印一下:
(lldb) p $11.ivars (const ivar_list_t *const) $12 = 0x00000001000022a8
继续对 ivar_list_t 类型的$12 取值:
(lldb) p *$12 (const ivar_list_t) $13 = { entsize_list_tt<ivar_t, ivar_list_t, 0> = { entsizeAndFlags = 32 count = 2 first = { offset = 0x0000000100002378 name = 0x0000000100001e64 "hobby" type = 0x0000000100001fa7 "@\"NSString\"" alignment_raw = 3 size = 8 } } } (lldb) p $.first (const ivar_t) $13 = { offset = 0x0000000100002378 name = 0x0000000100001e64 "hobby" type = 0x0000000100001fa7 "@\"NSString\"" alignment_raw = 3 size = 8 }
可以看到成员变量存在类的 ivars 属性里
3. 类的方法
3.1 类的实例方法
我们继续对类进行代码编辑,对Person
类添加实例方法和类方法
- (void)sayHello; // 实例方法
+ (void)sayHappy; // 类方法
还是对类结构$5 进行解析,这次我们打印它的 methods 属性
(lldb) p $5.methods
(method_array_t) $14 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002240
arrayAndFlag = 4294976064
}
}
}
得到的是method_array_t 类型的$14, 是个数组类型,继续打印:
(lldb) p $14.list
(method_list_t *) $15 = 0x0000000100002240
(lldb) p *$15
(method_list_t) $16 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "sayHello"
types = 0x0000000100001f8c "v16@0:8"
imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
}
}
可见,实例方法sayHello
方法,存储在类的methods属性里
3.2 类的类方法存储
类方法在上述的过程中没有看到,那么它会在哪里呢?
为了找到它,我们重新回到最初的pClass
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da280 0x0000000000000000
得到isa值为0x001d800100002389,这时,唤出它的掩码 ISA_MASK 来获取它的元类
(lldb) p/x 0x001d800100002389 & 0x0000000ffffffff8
(long) $1 = 0x0000000100002388
继续对元类$1的结构进行解析:
(lldb) x/4gx 0x0000000100002388
0x100002388: 0x001d800100b370f1 0x0000000100b370f0
0x100002398: 0x0000000100f946c0 0x0000000100000003
可知0x100002388是这个元类的地址,根据上文提到的内存便宜,我们继续加上2个16进制位,得到0x1000023a8,p一下:
(lldb) p (class_data_bits_t *)$2
(class_data_bits_t *) $3 = 0x00000001000023a8
继续执行上文实例方法类似的方案,执行data() — 取值 – 获取methods 的思路,具体代码如下:
(lldb) p $3->data()
(class_rw_t *) $4 = 0x0000000100f94620
(lldb) p *$4
(class_rw_t) $5 = {
flags = 2685075456
version = 7
ro = 0x00000001000021f8
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000021d8
arrayAndFlag = 4294975960
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff942e6990
demangledName = 0x0000000000000000
}
(lldb) p $5.methods
(method_array_t) $10 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000021d8
arrayAndFlag = 4294975960
}
}
}
(lldb) p $10.list
(method_list_t *) $11 = 0x00000001000021d8
(lldb) p *$11
(method_list_t) $12 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayHappy"
types = 0x0000000100001f8c "v16@0:8"
imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
}
}
}
(lldb)
可见,类方法sayHappy
方法,存储在类的元类 methods属性里
4. 总结
- 类的本质上一个类型为objc_class的结构体,包含有isa、父类、属性、成员变量及方法列表等
- 类属性存在结构体的properties里
- 类的成员变量存在结构体的 ivars里
- 类的实例方法,存在结构体 的methods 属性里
- 类的类方法,存在父类的class_ro_t 里的methods里
如下图示: