前面几篇文章中,我们学习了对象的创建、内存布局、isa的结构和走位,知道了对象通过isa关联到类。本文就开始学习类的原理。
1. 类的本质
1.1 类的本质
想要分析OC中类的结构,我们可以通过clang命令得到底层的实现:
clang -rewrite-objc main.m -o main.cpp
阅读cpp文件发现:Class真正类型是objc_class。
typedef struct objc_class *Class;
下面我们就看下objc_class类型的结构体的实现:
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();
}
...
};
objc_class继承于objc_object
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
从objc_class结构体,我们可以看出objc_class主要由superclass、cache、bits组成的。objc_class继承自objc_object
> 结论:类的本质是objc_class类型的结构体,objc_class继承于objc_object,所以满足万物皆对象
1.2 objc_object 和 NSObject的关系
继承于objc_object就满足万物皆对象呢?
我们来查看下NSObject源码
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
> NSObject是OC版本的objc_object,和objc_object的定义是一样的,在底层会编译成objc_object
2. 类的结构
我们已经学习了类的本质、类的继承关系,那么我们定义的属性和方法存放在类结构哪里呢?
通过objc_class源码可以得出,类有4个属性:isa、superclass、cache、bits。
2.1 Class ISA
实例对象通过isa关联类,类对象通过isa关联元类。Class是个指针,占用8字节。
2.2 Class superClass
类的父类,一般为NSObject。Class类型的,占用8字节
2.3 cache_t cache
cache主要是用来存储方法缓存链表_buckets,和mask(分配用来缓存bucket的总数),还有当前实际占用的bucket个数。 16字节。
struct cache_t {
struct bucket_t *_buckets; // 8 结构体指针
mask_t _mask; // 4
mask_t _occupied; // 4
...
};
2.4 class_data_bits_t bits
- 先看bits的定义
struct class_data_bits_t {
uintptr_t bits;
private:
bool getBit(uintptr_t bit)
{
return bits & bit;
}
...
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
...
};
源码看着是懵懵的,但是通过data字段,隐约推断出是存放数据的地方。
- 另外,在objc_class结构体中,有一句注释:class_data_bits_t bits 相当于 class_rw_t * plus custom rr/alloc flags
// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags)
// class_rw_t * plus custom rr/alloc flags
我们可以理解为,bits中主要有两部分:
- 为我们提供了便捷方法用于返回其中的 class_rw_t * 指针:
- 一个long类型的bits标志位,这个标志位存储了很多flags(比如:快速分配内存标志、是否有析构函数、是否有构造函数、是否有自定义控件等)
- 再看一下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;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
...
}
看到了什么? methods、properties、protocols。这里我们可以大胆猜测,bits中可能存放有我们定义的属性以及方法。但是一切都是猜测,下面我们就验证一下。
2. 类的属性和方法
- 为AKPerson添加成员属性、属性变量、类方法、实例方法。
@interface AKPerson : NSObject {
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)personInstanceMethed;
+ (void)personClassMethod;
@end
@implementation AKPerson
- (void)personInstanceMethed {}
+ (void)personClassMethod {}
@end
- x/4gx 打印clz的内容
(lldb) x/4gx clz
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da280 0x0000000000000000
(lldb)
- 其中isa8字节,superClass8字节,cache16字节,首地址0x1000023b0偏移32个字节,得出bits的内容。
(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $2 = 0x00000001000023d0
- 根据class_rw_t *data() { return bits.data(); }打印bits.data()
(lldb) p $2->data()
(class_rw_t *) $4 = 0x0000000100fd3150
(lldb) p *$4
(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
}
2.1 查找属性
打印ro.properties得到property_array_t。property_array_t为二维数组,探索其内部的list,打印property_array_t.list.first。
(lldb) p $5.properties
(property_array_t) $6 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000022f0
arrayAndFlag = 4294976240
}
}
}
(lldb) p $6.list
(property_list_t *) $7 = 0x00000001000022f0
(lldb)
(lldb) p $7.first
(property_t) $8 = (name = "name", attributes = "T@\"NSString\",C,N,V_Name")
Fix-it applied, fixed expression was:
$7->first
(lldb)
// entsize_list_tt and property_list_t
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
}
至此,我们在rw.properties找到了属性name。
2.2 查找成员变量
属性我们顺利找到了,但是成员变量呢?
来看看rw的ro源码
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;
}
};
发现了什么?const ivar_list_t * ivars,再次隐约感觉就是它了,继续测试。
(lldb) p $6.ro
(const class_ro_t *) $4 = 0x0000000100002308
(lldb) p *$7
(const class_ro_t) $8 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001f89 "\x02"
name = 0x0000000100001f80 "AKPerson"
baseMethodList = 0x0000000100002240
baseProtocols = 0x0000000000000000
ivars = 0x00000001000022a8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000022f0
}
(lldb) p $8.ivars
(const ivar_list_t *const) $7 = 0x00000001000022a8
(lldb) p *$9
(const ivar_list_t) $10 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x0000000100002378
name = 0x0000000100001e64 "hobby"
type = 0x0000000100001fa6 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
至此,我们在现ro.rw.ivar_list_t找到了成员变量。
2.3 查找实例方法
- 同理,我们p rw.methods.list找到实例方法
(lldb) p $5.methods
(method_array_t) $12 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002240
arrayAndFlag = 4294976064
}
}
}
(lldb) p $12.list
(method_list_t *) $13 = 0x0000000100002240
(lldb) p *$13
(method_list_t) $14 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "personInstanceMethed"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001b90 (2`-[AKPerson personInstanceMethed] at AKPerson.m:25)
}
}
}
- 但是发现method_t的count = 4。也就是还有另外3个方法,继续打印发现其他三个方法分别为C++的析构函数destruct方法,属性name的getter和setter方法
(lldb) p $14.get(1)
(method_t) $15 = {
name = ".cxx_destruct"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001c60 (2`-[AkPerson .cxx_destruct] at AkPerson.m:23)
}
(lldb) p $14.get(2)
(method_t) $16 = {
name = "name"
types = 0x0000000100001f93 "@16@0:8"
imp = 0x0000000100001bf0 (2`-[AkPerson name] at AkPerson.h:29)
}
(lldb) p $14.get(3)
(method_t) $17 = {
name = "setName:"
types = 0x0000000100001f9b "v24@0:8@16"
imp = 0x0000000100001c20 (2`-[AkPerson setName:] at AkPerson.h:29)
}
至此,我们在现rw.method_list_t找到了实例方法。
2.4 查找类方法
浏览rw/ro的源码,没有发现类方法的存储位置。而objc_class的其他成员superClass/cache明显也不是。只剩下isa。根据isa的走位,父子类的isa最后都指向根元类。那么类方法是不是在根元类呢?
通过isa & mask找到根元类,然后p rw.method_list_t.list.
(lldb) p $26.list
(method_list_t *) $27 = 0x00000001000021d8
(lldb) p *$27
(method_list_t) $28 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayHappy"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001bc0 (2`+[AKPerson classMethod] at AKPerson.m:27)
}
}
}
2.5 总结
- 属性存在 类 的 bit.data.rw.property
- 成员变量存在 类 的bit.data.rw.ro.ivar
- 实例方法存在 类 的 bit.data.rw.method
- 类方法以实例方法的形式,存在元类。元类又继承自NSObject,形成一个闭环。
2.6 clang验证
3. 参考资料
- NSObject结构图