前言
在上篇文章,我们看到NSObject是由objc_class组成,我objc_class都包含什么信息,我们平常用的属性,方法,协议都在哪里存储呢,今天来学习下
objc_class内部结构
先看下源码:
struct objc_class : objc_object {
// 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_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
...
}
通过源码可以看出,objc_class 包含了isa,父类superclass,cache_t,class_data_bits_t,从字面意思,可以看出class_data_bits_t是存储类数据信息的,而且还提供了方法 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,还有一个ro属性,进去class_ro_t看下源码:
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;
}
};
这里面也有类的一些信息,那哪个才是真正存储信息的呢? 可以代码验证下
代码验证
先定义一个类,里面有变量,有属性,有类方法,和实例方法
@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
// 定义方法,断点输出
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
NSLog(@"%@", pClass);
}
拿到class_rw_t
打开断点调试,我们先输出pClass的地址信息
(lldb) x/4gx pClass
0x100001200: 0x001d8001000011d9 0x0000000100afd140
0x100001210: 0x00000001003a0270 0x0000000000000000
通过源码可以看出我们想要拿到bits,得先找到地址
isa // 8个字节
Class superclass; // 8个字节
cache_t cache; // 16 个字节
可以看出,bits地址是objc_class的地址上偏移32个字节,此时objc_class地址是0x100001200,偏移32个字节,就是 0x100001220,那我们输出下内容
(lldb) p (class_data_bits_t*)0x100001220
(class_data_bits_t *) $1 = 0x0000000100001220
// 通过方法拿到class_rw_t指针
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010183e7d0
// 获取指针里面内容
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148139008
version = 0
ro = 0x0000000100001178
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000010c8
arrayAndFlag = 4294971592
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100001160
arrayAndFlag = 4294971744
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSDate
demangledName = 0x0000000000000000
}
拿到class_rw_t里面的方法列表
可以看出,方法,属性都在这里,我们试着看方法列表:
lldb) p $3.methods
(method_array_t) $8 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000010c8
arrayAndFlag = 4294971592
}
}
}
(lldb) p $8.list
(method_list_t *) $9 = 0x00000001000010c8
(lldb) p *$9
(method_list_t) $10 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 3
first = {
name = "nickName"
types = 0x0000000100000f97 "@16@0:8"
imp = 0x0000000100000de0 (LGTest`-[LGPerson nickName] at LGPerson.h:16)
}
}
}
拿到ro里面的信息
拿到了我们想要的内容,但是我们想找到hobby这个内部属性,发现在这里找不到,刚看源码发现ro里面也有信息,我们继续找
(lldb) p $3.ro
(const class_ro_t *) $4 = 0x0000000100001178
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100000f59 "\x02"
name = 0x0000000100000f50 "LGPerson"
baseMethodList = 0x00000001000010c8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100001118
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100001160
}
看出来,里面除了baseProperties,baseMethodList,baseProtocols,还有ivars,我们验证下内容
(lldb) p $5.ivars
(const ivar_list_t *const) $16 = 0x0000000100001118
(lldb) p *$16
(const ivar_list_t) $17 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000011c8
name = 0x0000000100000f7f "hobby"
type = 0x0000000100000faa "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
找到hobby这个属性变量了,当然方法列表也能在这里找到。所以,我们知道了类的属性,实例方法,都存贮在这里。
类方法存贮位置
我们发现 + (void)sayHappy; 这个还没有找到存贮,我们之前讲过类都有元类,那会不会在元类里存储呢,代码验证下
void testInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
NSLog(@"%s",__func__);
}
void testClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); // ?
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
NSLog(@"%s",__func__);
}
输出内容为:
0x100002248-0x0-0x0-0x1000021e0
0x0-0x0-0x1000021e0-0x1000021e0
可以看出,类方法是存在于元类的里面,我们也可以自己验证下
// 当前类信息
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100afe140
0x1000023c0: 0x00000001003a1290 0x0000000000000000
// 获取元类
(lldb) p 0x001d800100002389&0x00007ffffffffff8
(long) $1 = 4294976392
(lldb) po $1
LGPerson
// 获取元类地址
(lldb) x/4gx $1
0x100002388: 0x001d800100afe0f1 0x0000000100afe0f0
0x100002398: 0x000000010192ac00 0x0000000100000003
// 获取元类class_rw_t
(lldb) p (class_data_bits_t*)0x1000023a8
(class_data_bits_t *) $2 = 0x00000001000023a8
(lldb) p $2->data()
(class_rw_t *) $3 = 0x000000010192ab60
(lldb) p *$3
(class_rw_t) $4 = {
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 = 0x00007fff95935900
demangledName = 0x0000000100001f80 "LGPerson"
}
// 获取元类ro
(lldb) p $4.ro
(const class_ro_t *) $5 = 0x00000001000021f8
(lldb) p *S5
error: use of undeclared identifier 'S5'
(lldb) p $5
(const class_ro_t *) $5 = 0x00000001000021f8
(lldb) p *$5
(const class_ro_t) $6 = {
flags = 389
instanceStart = 40
instanceSize = 40
reserved = 0
ivarLayout = 0x0000000000000000
name = 0x0000000100001f80 "LGPerson"
baseMethodList = 0x00000001000021d8
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
}
// 获取元类方法列表
(lldb) p $6.baseMethodList
(method_list_t *const) $7 = 0x00000001000021d8
(lldb) p *$7
(method_list_t) $8 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayHappy"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
}
}
}
通过信息可以看出来,类方法确实存在于元类中。