通过之前的文字介绍,我们知道了对象是如何创建的,也知道了对象怎么通过 isa 找到类的,那么下一步我们将会对类进行进一步探索。
一:探索准备
通常我们探索OC的一些底层实现都是通过将OC转化为C++的代码,下面我们操作一下
以下为我们的测试代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
WYPerson *p = [WYPerson alloc];
Class pClass = [p class];
NSLog(@"%@ --- %p",p,pClass);
}
return 0;
}
1.1 获取main.cpp
在MAC终端定位到 main.m 所在文件夹,输入:clang -rewrite-objc main.m
clang -rewrite-objc main.m
之后 main.m 所在文件夹会多出一个 main.cpp 的文件
1.2 查看main.cpp
打开这个 main.cpp,你会发现有10000多行的代码,我们把代码拉到最后,最终我们的测试代码会转化成如下形式
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
WYPerson *p = ((WYPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("WYPerson"), sel_registerName("alloc"));
Class pClass = ((Class (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("class"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x3_cw7r9gxj7rnfg6mvdl0tlksh0000gn_T_main_f5957c_mi_0,p,pClass);
}
return 0;
}
我们想看的是这个转化后的Class是怎么定义的,在main.cpp中我们发现其实Class就是一个objc_class类型
typedef struct objc_class *Class;
下面我们配合objc的源码来看下objc_class究竟为何物
二: objc_class结构源码分析
2.1 源码定义
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();
}
.............此处省略
}
2.2 成员分析
-
- isa 之前已经探究过,这里被注释说明是继承过来的,可以看到后面的
objc_object,这也说明其实类也是个对象。
- isa 之前已经探究过,这里被注释说明是继承过来的,可以看到后面的
-
- superclass 父类指针。
-
- 类的相关缓存。
-
class_data_bits_t类型的bits,存储的是类的一些信息
-
class_rw_t指针,其中的bits就是第四点中的bits
三:成员变量
通常我们会这样定义一个成员变量,那这个成员变量存在于哪里呢?
@interface WYPerson : NSObject
{
NSString *_title;
}
@end
3.1 runtime的已知方法
我们用class_copyIvarList获取ivars并且打印出来
WYPerson *p1 = [WYPerson alloc];
Class pClass = [p1 class];
NSLog(@"%@ -- %p",p1,pClass);
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(pClass, &count);
for (unsigned int i = 0; i < count; i++) {
Ivar const ivar = ivars[i];
const char *tempIvarName = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:tempIvarName];
NSLog(@"ivar --- %@",ivarName);
}
控制台打印出了我们定义的_title
2020-03-15 21:20:56.222653+0800 objc-test[87373:11596730] <WYPerson: 0x10182b7a0> -- 0x1000013a0
2020-03-15 21:20:56.223666+0800 objc-test[87373:11596730] ivar --- _title
本着对知识刨根问底的精神我们不禁会想,class_copyIvarList怎么做到的呢?我们来通过源码看下。
class_copyIvarList(Class cls, unsigned int *outCount)
{
const ivar_list_t *ivars;
Ivar *result = nil;
unsigned int count = 0;
if (!cls) {
if (outCount) *outCount = 0;
return nil;
}
mutex_locker_t lock(runtimeLock);
assert(cls->isRealized());
if ((ivars = cls->data()->ro->ivars) && ivars->count) {
result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar));
for (auto& ivar : *ivars) {
if (!ivar.offset) continue; // anonymous bitfield
result[count++] = &ivar;
}
result[count] = nil;
}
if (outCount) *outCount = count;
return result;
}
其中(ivars = cls->data()->ro->ivars)非常关键,系统会去这里读取数据,cls->data()实际上是上文提到的class_rw_t,ro就是class_rw_t中class_ro_t
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_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;
......
};
所以调用class_copyIvarList方法实则返回的就是class_ro_t中的ivars,这是一个ivar_list_t类型。
3.2 获取内存偏移
从上面的分析可以看出,只需要获取到bits,一切就将迎刃而解。
我们都知道数组指针的地址也是数组第一个元素的地址,既然如此我们获取到Class的地址,同样也是首个元素isa的地址,然后进行偏移,就能获取到class_rw_t,那偏移量是多少呢?
- 第一个元素:isa,前面分析过,8字节。
- 第一个元素:superclass,是个结构体指针,8字节。
- 第一个元素:cache,是个结构体,16字节。
这里解释下16字节怎么来的。
struct cache_t {
struct bucket_t *_buckets; // 结构体 8字节
mask_t _mask; // mask_t 是 uint32_t 4字节
mask_t _occupied; // 4字节
......
}
第一个成员_buckets,是个指针,占8字节,第二个成员_mask和第三个成员_occupied,都是uint32_t类型,都是占4字节,总共就是16字节
总偏移量 = 8(isa) + 8(superclass) + 16(cache) = 32字节
3.3 通过内存偏移获取ivars
获取pClass(类)的地址
WYPerson *p1 = [WYPerson alloc];
Class pClass = [p1 class];
NSLog(@"%@ -- %p",p1,pClass);
(lldb) p/x pClass
(Class) $0 = 0x00000001000013a0 WYPerson
(lldb)
上面我们算出要偏移32字节,将十进制的32转化为十六进制就是0x20, 0x00000001000013a0 + 0x20 = 0x00000001000013A0,最终0x00000001000013A0就指向我们要找的bits。这里我们将其强转为class_data_bits_t
(lldb) p (class_data_bits_t *)0x00000001000013A0
(class_data_bits_t *) $1 = 0x00000001000013a0
得到的变量 $1 = 0x00000001000013a0 就是 bits 的地址,下一步是获取 bits.data(),其返回类型为 class_rw_t 的指针:
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100001378
下面我们打印出$2中的内容
(lldb) p *$2
(class_rw_t) $3 = {
flags = 11526385
version = 1933313
ro = 0x0000000100afe0f0
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x000000010192a530
arrayAndFlag = 4321355056
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100000003
arrayAndFlag = 4294967299
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x000000010192a490
arrayAndFlag = 4321354896
}
}
}
firstSubclass = 0x001d800100001379
nextSiblingClass = NSObject
demangledName = 0x000000010192a580 "\350I\332"
}
下一步取ro
(lldb) p $3.ro
(const class_ro_t *) $5 = 0x0000000100afe0f0
打印ro的内容
(lldb) p *$5
(const class_ro_t) $6 = {
flags = 11526385
instanceStart = 1933313
instanceSize = 11526464
reserved = 1
ivarLayout = 0x00000001018061d0 "d\3439
name = 0x0000000300000003 ""
baseMethodList = 0x0000000101900130
baseProtocols = 0x001d800100afe0f1
ivars = 0x0000000100afe0f0
weakIvarLayout = 0x00000001003a0290 ""
baseProperties = 0x0000000000000000
其实到此为止我们已经找到ivars了,你以为结束了吗?当然......继续打印
(lldb) p $6.ivars
(const ivar_list_t *const) $8 = 0x0000000100afe0f0
(lldb) p *$8
(const ivar_list_t) $9 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 1
first = {
offset = 0x0000000100001308
name = 0x0000000100000f76 "_title"
type = 0x0000000100000fa1 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
此时我们就看到_title了,这也就验证了类的成员变量保存在bits.data()->ro->ivars。
四:方法
4.1 实例方法
定义一个方法
@interface WYPerson : NSObject
{
NSString *_title;
}
- (void)eat;
@end
@implementation WYPerson
-(void)eat
{
NSLog(@"eat");
}
@end
上面我们打印了ivars,里面除了ivars,还有其他的一些成员,那既然我们要找方法,不妨我们像上面一样直接打印baseMethodList
(lldb) p $8.baseMethodList
(method_list_t *const) $11 = 0x0000000100001240
(lldb) p *$11
(method_list_t) $12 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "eat"
types = 0x0000000100000f99 "v16@0:8"
imp = 0x0000000100000d10 (objc-test`-[WYPerson eat] at WYPerson.m:12)
}
}
}
一目了然,这样我们就找到了实例方法,它存储在bits.data()->ro-> baseMethodList中。
4.2 类方法
4.2.1 发现问题
定义一个方法
@interface WYPerson : NSObject
{
NSString *_title;
}
- (void)eat;
+ (void)smile;
@end
@implementation WYPerson
-(void)eat
{
NSLog(@"eat");
}
+ (void)smile{
NSLog(@"smile");
}
@end
我们来继续打印
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "eat"
types = 0x0000000100000f86 "v16@0:8"
imp = 0x0000000100000be0 (objc-test`-[WYPerson eat] at WYPerson.m:12)
}
}
}
(lldb) p $7.get(0)
(method_t) $8 = {
name = "eat"
types = 0x0000000100000f86 "v16@0:8"
imp = 0x0000000100000be0 (objc-test`-[WYPerson eat] at WYPerson.m:12)
}
(lldb) p $7.get(1)
(method_t) $9 = {
name = ".cxx_destruct"
types = 0x0000000100000f86 "v16@0:8"
imp = 0x0000000100000cb0 (objc-test`-[WYPerson .cxx_destruct] at WYPerson.m:11)
}
(lldb) p $7.get(2)
Assertion failed: (i < count), function get, file /Users/wangyun/Desktop/iOS底层研究代码/OC对象原理1/资料/objc4-750/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
我们打印出了所有的方法,发现有·eat方法,以及系统在底层添加的.cxx_destruct方法,唯独没有我们的smile。怎么办?
此时你会不会这样想:既然对象的属性和方法存储在类里面,那么作为类,它也是个对象,那么它的方法会不会存在于元类当中呢?
4.2.2 验证猜想
我们先获取到元类
(lldb) x/4xg pClass
0x100001498: 0x001d800100001471 0x0000000100afe140
0x1000014a8: 0x0000000101862840 0x0000000100000003
(lldb) p/x 0x001d800100001471 & 0x00007ffffffffff8
(long) $1 = 0x0000000100001470
然后去获取元类的baseMethodList
(lldb) p $6.baseMethodList
(method_list_t *const) $7 = 0x0000000100001228
(lldb) p *$7
(method_list_t) $8 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "smile"
types = 0x0000000100000f86 "v16@0:8"
imp = 0x0000000100000c10 (objc-test`+[WYPerson smile] at WYPerson.m:17)
}
}
}
皇天不负有心人,我们在 元类 的baseMethodList里面找到了smile的方法,而且我们发现不同于类,元类中系统底层不会额外添加方法。
五:属性
5.1 @property的本质
@interface WYPerson : NSObject
{
NSString *_title;
}
@property(nonatomic,copy) NSString * name;
- (void)eat;
@end
@property(nonatomic,copy) NSString * name;这行代码会做什么事情呢?
-
- 生成
setter方法。
- 生成
-
- 生成
getter方法。
- 生成
-
- 生成带下划线的成员变量,也就是
_name。
- 生成带下划线的成员变量,也就是
5.2 验证
按照上面的指令打印
(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000012b8
(lldb) p *$6
(const ivar_list_t) $7 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x0000000100001380
name = 0x0000000100000f61 "_title"
type = 0x0000000100000fa5 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
没有_name,但是发现返回ivar_list_t类型,它继承自entsize_list_tt,我们可以调用get(),传递索引来获取.
(lldb) p $7.get(0)
(ivar_t) $8 = {
offset = 0x0000000100001380
name = 0x0000000100000f61 "_title"
type = 0x0000000100000fa5 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
offset = 0x0000000100001388
name = 0x0000000100000f68 "_name"
type = 0x0000000100000fa5 "@\"NSString\""
alignment_raw = 3
size = 8
}
到这里我们发现的确生成了成员变量_name.
(lldb) p $11.get(2)
(method_t) $14 = {
name = "name"
types = 0x0000000100000f92 "@16@0:8"
imp = 0x0000000100000c50 (objc-test`-[WYPerson name] at WYPerson.h:16)
}
(lldb) p $11.get(3)
(method_t) $15 = {
name = "setName:"
types = 0x0000000100000f9a "v24@0:8@16"
imp = 0x0000000100000c80 (objc-test`-[WYPerson setName:] at WYPerson.h:16)
}
到这里也验证了的确生成了setter和getter方法。
5.3 属性与成员变量对比
此时我们不妨再来打印下baseProperties
(lldb) p $5.baseProperties
(property_list_t *const) $16 = 0x0000000100001300
(lldb) p *$16
(property_list_t) $17 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}
由此我们可以推断,当添加完属性,除了生成setter方法,getter方法,_name成员变量之外,好会将属性保存在class_ro_t中。而添加的成员变量不会。
六: 总结
- 成员变量:保存在类的
ivars中:objc_class->bits.data()->ro->ivars - 属性:自动添加成员变量保存在
ivars中:objc_class->bits.data()->ro->ivars getter和setter方法保存在baseMethodList中:objc_class->bits.data()->ro-> baseMethodList- 属性信息还另外单独保存在
baseProperties中:objc_class->bits.data()->ro-> baseProperties - 实例方法:保存在类的
baseMethodList中:objc_class->bits.data()->ro-> baseMethodList - 类方法:保存在元类的
baseMethodList中:metaclass->bits.data()->ro-> baseMethodList