前言
本文基于objc4-781源码进行分析
首先我们创建一个Person对象个对象,里面有一些属性、实例方法以及类方法
案例
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)sayWord;
@end
@implementation Person
- (void)sayHello {
NSLog(@"Hello, ");
}
+ (void)sayWord {
NSLog(@"World!");
}
@end
我们通过lldb来调式一下玩玩
//首先打印一下objc
(lldb) po objc
<Person: 0x10079b9b0>
//读取一下objc的内存情况
(lldb) x/4gx objc
0x10079b9b0: 0x001d8001000021cd 0x0000000000000000
0x10079b9c0: 0x697263534b575b2d 0x67617373654d7470
//拿到objc首地址
(lldb) p/x objc
(Person *) $2 = 0x000000010079b9b0
//根据之前的内容,通过首地址&0x00007ffffffffff8ULL,获取类的指针地址
(lldb) p/x 0x001d8001000021cd & 0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x00000001000021c8
//打印一下这个类,果然是Person
(lldb) po 0x00000001000021c8
Person
//读取0x00000001000021c8这个内存情况
(lldb) x 0x00000001000021c8
0x1000021c8: a0 21 00 00 01 00 00 00 40 41 33 00 01 00 00 00 .!......@A3.....
0x1000021d8: d0 61 64 00 01 00 00 00 07 00 00 00 1c 80 04 00 .ad.............
//通过调用 Person.class方式获取Person类
(lldb) x Person.class
0x1000021c8: a0 21 00 00 01 00 00 00 40 41 33 00 01 00 00 00 .!......@A3.....
0x1000021d8: d0 61 64 00 01 00 00 00 07 00 00 00 1c 80 04 00 .ad.............
//或者objc_getClass方式
(lldb) x object_getClass(objc)
0x1000021c8: a0 21 00 00 01 00 00 00 40 41 33 00 01 00 00 00 .!......@A3.....
0x1000021d8: d0 61 64 00 01 00 00 00 07 00 00 00 1c 80 04 00 .ad.............
发现以上三种方式读出来的内存信息是一样
//读取0x00000001000021c8的内存情况
(lldb) x/4gx 0x00000001000021c8
0x1000021c8: 0x00000001000021a0 0x0000000100334140
0x1000021d8: 0x00000001006461d0 0x0004801c00000007
//发现还是Person,这里已经是Person类的元类
(lldb) po 0x00000001000021a0
Person
//元类的指针地址&0x00007ffffffffff8ULL
(lldb) p/x 0x00000001000021a0 & 0x00007ffffffffff8ULL
(unsigned long long) $9 = 0x00000001000021a0
//发现还是Person
(lldb) po 0x00000001000021a0
//我们读取Person元类的内存信息
(lldb) x/4gx 0x00000001000021a0
0x1000021a0: 0x00000001003340f0 0x00000001003340f0
0x1000021b0: 0x00000001007a3a60 0x0004e03500000007
//打印元类的首地址
(lldb) po 0x00000001003340f0
NSObject
//把NSObject地址&0x00007ffffffffff8ULL得到存储类信息的地址
(lldb) p/x 0x00000001003340f0 & 0x00007ffffffffff8ULL
(unsigned long long) $16 = 0x00000001003340f0
打印 0x00000001003340f0,发现还是NSObject
(lldb) po 0x00000001003340f0
NSObject
//读取NSObject内存信息,发现首地址和NSObject一样
(lldb) x/4gx 0x00000001003340f0
0x1003340f0: 0x00000001003340f0 0x0000000100334140
0x100334100: 0x00000001007a3ba0 0x0004e03100000007
//打印首地址,还是NSObject
(lldb) po 0x00000001003340f0
NSObject
从这里我们可以看出上面验证的结果符合这个经典的isa走位图
)
从图中可以看出isa的走位
- 对象 的 isa 指向 类(也可称为类对象)
- 类 的 isa 指向 元类
- 元类 的 isa 指向 根元类,即NSObject
- 根元类 的 isa 指向 它自己
父子类继承关系的走位即Superclass的走位
- 子类subClass继承自父类superClass
- 父类superClass继承自 根类RootClass,根类是指NSObject
- 根类NSObject继承自 nil
元类也存在继承,元类之间的继承关系如下
- 子类的元类metal SubClass继承自父类的元类metal SuperClass
- 父类的元类metal SuperClass继承自根元类Root metalClass
- 根元类Root metal Class继承于根类RootClass,此时的根类是指NSObject
子类的实例Instance of Subclass和父类的实例Instance of Superclass 以及根类的实例Instance of Root class之间是没有任何关系
那么NSObject是有一个还是多个,我们来验证一下
Class objc1 = [Person class];
Class objc2 = [Person alloc].class;
Class objc3 = object_getClass([Person alloc]);
NSLog(@"\n%p-\n%p-\n%p", objc1, objc2, objc3);
打印结果如下:
0x1000021d8-
0x1000021d8-
0x1000021d8
这里可以验证类只会存在一份
关于objc_class与objc_object
之前的文章中我们知道 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() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
ASSERT(isFuture() || isRealized());
data()->setFlags(set);
}
.........
我们看到objc_class是继承objc_object,objc_class里面是没有isa,我们接着看objc_object
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
.........
}
这里我看到了isa_t isa,这里我们可以得出,OC里面所有的Class都是以objc_class为模板创建的。这就是为什么所有的类都有isa,并且非常重要
早期的objc_class结构体和现在不一样,从源码里可以看到是这样的
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
但现在已经被废弃了,我们现在这个是开头就说的objc4-781最新优化的,我们后面的类的结构分析也是基于新版来分析。
类结构的探索
接下来我们通过lldb看看类的存储是怎么样的? 首先读取一下类的首地址
(lldb) p/x Person.class
打印输出
(Class) $2 = 0x00000001000021d8 Person
在读取一下类的内存信息
(lldb) x/4gx 0x00000001000021d8
打印结果
0x1000021d8: 0x00000001000021b0 0x0000000100334140
0x1000021e8: 0x0000000100669ff0 0x0002801c00000003
根据objc_class的源码,我们通过首地址偏移32字节找到bits,
//0x1000021d8+32 = 0x1000021f8
(lldb) p (class_data_bits_t *)0x1000021f8
打印结果
(class_data_bits_t *) $3 = 0x00000001000021f8
调用bits里面的data方法
(lldb) p $3->data()
得到class_rw_t 类型的data数据
(class_rw_t *) $5 = 0x0000000100669f90
打印data数据
(lldb) p *$5
结果如下
(class_rw_t) $6 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975664
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
在这里我们没有看到属性、方法等信息,接下来探索一下属性、方法,通过源码中我们看到class_rw_t中有相关方法
我们调用一下试试,上面我们已经拿到了class_rw_t类型的data
调用属性列表的方法
(lldb) p $6.properties()
打印结果
(const property_array_t) $7 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002188
arrayAndFlag = 4294975880
}
}
}
我们看到有个list,打印一下list
(lldb) p $7.list
输出
(property_list_t *const) $8 = 0x0000000100002188
拿到$8地址并打印
(lldb) p *$8
结果
(property_list_t) $9 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}
都这里我们已经看到属性name的相关信息了,读取数组的第一个元素
(lldb) p $9.get(0)
打印结果
(property_t) $10 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
接着往下读取
(lldb) p $9.get(1)
提示错误,提示越界了
Assertion failed: (i < count), function get, file /Users/wupin/Desktop/Logic/大师班/20200909-大师班第3天-OC对象下-资料/01--课堂代码/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
接下来我们根据上面的方式在看看成员列表 首先打印下内存首地址
(lldb) p/x Person.class
输出
(Class) $0 = 0x0000000100002200 Person
偏移32字节找到bits的地址
(lldb) p (class_data_bits_t *) 0x0000000100002220
输出
(class_data_bits_t *) $1 = 0x0000000100002220
调用data方法
(lldb) p $1->data()
输出
(class_rw_t *) $2 = 0x0000000101944010
打印data数据
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975664
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
获取bits中ro的地址
(lldb) p $2->ro()
输出
(const class_ro_t *) $5 = 0x00000001000020b0
获取ivars地址首地址
(lldb) p $5->ivars
输出
(const ivar_list_t *const) $7 = 0x0000000100002160
打印成员变量类别
(lldb) p *$7
输出结果可以看到happy
(const ivar_list_t) $8 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000021c8
name = 0x0000000100000f4a "happy"
type = 0x0000000100000f83 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
或者通过地址偏移也能找到带下划线的成员变量和自定义的成员变量
(lldb) p $8.get(0)
(ivar_t) $9 = {
offset = 0x00000001000021c8
name = 0x0000000100000f4a "happy"
type = 0x0000000100000f83 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $8.get(1)
(ivar_t) $10 = {
offset = 0x00000001000021d0
name = 0x0000000100000f50 "_name"
type = 0x0000000100000f83 "@\"NSString\""
alignment_raw = 3
size = 8
}
我们在看下实例方法,先打印下
p *$5
根据打印出的结构,有个baseMethodList
(const class_ro_t) $11 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100000f40 "\x02"
name = 0x0000000100000f39 "Person"
baseMethodList = 0x00000001000020f8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100002160
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000021a8
_swiftMetadataInitializer_NEVER_USE = {}
}
找到baseMethodList首地址
(lldb) p $11.baseMethodList
输出
(method_list_t *const) $12 = 0x00000001000020f8
在读取下里面的内容
(lldb) p *$12
输出,sayHello是存储在这了
(method_list_t) $13 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "sayHello"
types = 0x0000000100000f7b "v16@0:8"
imp = 0x0000000100000d90 (KCObjc`-[Person sayHello])
}
}
}
这里可以看出通过{}定义的成员变量,会存储在类的bits属性中,通过bits --> data() -->ro() --> ivars获取成员变量列表,除了包括成员变量,还包括属性定义的成员变量。通过@property定义的属性,也会存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性
那么类方法存储在哪里呢?根据前面的所了解的,类是元类的实例,subClass的isa指向元类,那我们元类中去找找
//打印首地址
(lldb) p/x Person.class
输出 (Class) $0 = 0x00000001000021d8 Person //读取内存信息
(lldb) x/4gx 0x00000001000021d8
输出
0x1000021d8: 0x00000001000021b0 0x0000000100334140
0x1000021e8: 0x000000010063f060 0x0002801c00000003
//获取元类的首地址
(lldb) p/x 0x00000001000021b0 & 0x00007ffffffffff8ULL
输出
(unsigned long long) $1 = 0x00000001000021b0
//偏移32位获取元类里面bits 地址
(lldb) p (class_data_bits_t *) 0x00000001000021d0
输出
(class_data_bits_t *) $2 = 0x00000001000021d0
//取元类里面bits数据地址
(lldb) p $2->data()
输出
(class_rw_t *) $4 = 0x000000010063efd0
打印bit数据
(lldb) p *$4
输出
(class_rw_t) $5 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975560
}
firstSubclass = nil
nextSiblingClass = 0x00007fff88436c60
}
//调用元类的方法列表
(lldb) p $5.methods()
输出
(const method_array_t) $8 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002090
arrayAndFlag = 4294975632
}
}
}
//获取方法列表list指针地址
(lldb) p $8.list
输出
(method_list_t *const) $9 = 0x0000000100002090
//获取方法列表的数据,这里我们看到了sayWord这个类方法
(lldb) p *$9
输出
(method_list_t) $10 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayWord"
types = 0x0000000100000f81 "v16@0:8"
imp = 0x0000000100000d80 (KCObjc`+[Person sayWord])
}
}
}
//或者通过下面这种方式访问
(lldb) p $10.get(0)
输出
(method_t) $11 = {
name = "sayWord"
types = 0x0000000100000f81 "v16@0:8"
imp = 0x0000000100000d80 (KCObjc`+[Person sayWord])
}
总结:
-
类的实例方法存储在类的bits属性中,通过bits --> methods() --> list获取实例方法列表,例如Persong类的实例方法sayHello 就存储在 Person类的bits属性中,类中的方法列表除了包括实例方法,还包括属性的set方法 和 get方法
-
类的类方法存储在元类的bits属性中,通过元类bits --> methods() --> list获取类方法列表,例如Person中的类方法sayWord 就存储在Person类的元类(名称也是Person)的bits属性中