通过上一章节的学习,我们已经了解了对象的底层实现,那么类,NSObject在底层是以什么方式存在的呢,属性,方法,协议,都是怎么存在于类里面的,这一章节就来深入了解一下
传送门☞iOS底层探索-准备工作
传送门☞iOS底层学习-OC对象前世今生
准备工作
创建如下类
@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation LGPerson
- (void)sayHello{
NSLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"LGPerson say : Happy!!!");
}
@end
指针偏移
int a = 10;
int b = 10;
LGNSLog(@"%d -- %p",a,&a);
LGNSLog(@"%d -- %p",b,&b);
输出:
KC打印: 10 -- 0x7ffeefbff4fc
KC打印: 10 -- 0x7ffeefbff4f8
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [LGPerson alloc];
LGNSLog(@"%@ -- %p",p1,&p1);
LGNSLog(@"%@ -- %p",p2,&p2);
输出:
KC打印: <LGPerson: 0x100544c30> -- 0x7ffeefbff4f8
KC打印: <LGPerson: 0x100545a20> -- 0x7ffeefbff4f0
通过以上两个例子,可以得出
- 常量的是值拷贝,只是对值就行了赋值,地址不同
- 对象的是引用拷贝,各自开辟了内存空间,对象的地址不同,对象地址的指针地址也不相同

// 数组指针
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
NSLog(@"%p - %p - %p",d,d+1,d+2);
for (int i = 0; i<4; i++) {
// int value = c[i];
int value = *(d+i);
LGNSLog(@"%d",value);
}
NSLog(@"指针 - 内存偏移");

- 一个对象的地址就是首个元素的地址
- 通过对指针的偏移,可以得到相对应的元素值
类的底层实现
类的实现实在编译期就申请好内存了,所以我们需要可以在编译期查看类的底层实现 编译后通过MackOView可以看到,LGPerson类已经生成

LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
通过clang命令编译main文件后得到cpp文件,可以发现,Class的底层结构是objc_class,里面有一个废弃的isa指针,所以不做研究.

objc_class结构如下,是继承自objc_object,所以其实类也是一种对象,所谓万物皆对象

objc_object定义中,NSObject是OC对objc_object的仿写,结构是一样的

我们可以看到objc_class成员变量主要有一下四个
Class ISA:主要关联类Class superclass:父类cache_t cacheclass_data_bits_t bits
cache_t cache

cache_t cache为一个结构体,所占字节数也如图所示,共16字节,不足以存放类中的属性和方法,所以,属性方法等数据,一定存储在class_data_bits_t bits结构中
class_data_bits_t bits
属性方法等数据是存储在class_data_bits_t bits中,并且可以看到class_rw_t中的数据都是来自于此。且结构中存在属性,方法,协议列表等,因此遇到跟踪class_data_bits_t bits,可以通过指针偏移来获取到class_data_bits_t bits中的数据,通过结构可知,在首地址偏移32字节即可得到


我们可以通过LLDB命令和指针偏移,一步步来探索
首先打印出类的16进制存储

0x1000023b0偏移32字节,在16进制下也就是0x20,得到0x00000001000023d0,由于class_data_bits_t bits不是对象类型,所以需要强转一下,得到如下地址

class_rw_t结构的data,那么调用方法可得到class_rw_t中的内容如下


属性变量存储
通过上面打印出的class_rw_t中,我们可以看到,根据名称,属性应该是存储在properties中,通过命令打印可得下图

list_array_tt结构可得此为二维数组,且根据结构可得数据应该存储在list结构中

property_list_t,可得其继承自entsize_list_tt,打印其中的元素Element first;可以得到保存的类中保存的属性


成员变量存储
在上述探寻属性的过程中,我们并没有看到成员变量的保存,所以,成员变量不是保存在properties中,应该是存在其他的结构中,根据class_rw_t结构思考可得,ro中存储成员变量的可能性较大,所以按照上述流程探寻ro

const class_ro_t中存在一个ivars,成员变量应该在此

ivars,发现其中first中已经有了我们的属性hobby,验证成功

count我们发现为2,所以应该还有一个成员变量,我们可以打印发现是_nickName,所以说明属性会自动生成对应的下划线成员变量

实例方法存储
通过对class_rw_t研究,我们发现有一个methods元素,那么这个很有可能就是存放方法的列表,通过打印我们可得

list,可以发现我们的实例方法sayHello,此时已经验证了方法的存储

count为4,我们看一下剩下的是什么方法。我们发现是属性的get和set方法已经默认的析构方法,验证了属性会自动创建get和set

类方法存储
通过上面的存储探究,我们发现并没有类对象中并没有类方法的存储,且类继承子NSObject,且NSObject中并没有对应的类方法,所以,我们可以猜测,类方法是存在于元类之中的,通过类对象的isa可以找到元类


总结
通过上面的一系列探索,我们可得如下结论
- 万物皆对象,均继承自
objc_object - 属性和成员变量都存放在类的
class_rw_t结构体中 - 属性会自动生成get和set方法,成员变量不会
- 实例方法会存在于类中,而类方法会存在于元类之中,且是以实例方法的形式存在的