iOS之类的结构分析
万物皆对象
我们知道在iOS中,id
可以指向所有的实例对象,Class
可以指向所有的类,我们来看一下他们的声明:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// A pointer to an instance of a class.
typedef struct objc_object *id;
从声明中可以看出,OC的所有实例对象都是由objc_object
结构体扩展而来,我们知道objc_object
结构体中有一个isa
指针,所以所有的对象都有isa
指针。
我们再来看一下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();
}
省略。。。
}
从objc_class
的结构可以得出,OC中所有的类
其实也是对象
,也存在isa
指针,所以这就是我们常说的万物皆对象
。
对象、类、元类、根元类
在前一篇iOS之isa文章的最后,插入了一张图,如下:
类的继承.png很多iOS开发者,肯定不止一次见到过这张图,我想肯定有人会有疑问,真的像图中这样吗?下面我们验证一下。
@interface PYTeacher : NSObject
@end
@implementation PYTeacher
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
PYTeacher *teacher = [PYTeacher alloc];
NSLog(@"%@", teacher);
}
return 0;
}
依然是上面这段代码,我们可以通过打印对象的内存分布来验证,依然以x86_64
为例,ISA_MASK
为0x00007ffffffffff8ULL
:
对象的 isa
指向类
(lldb) x/2gx teacher
0x100712980: 0x001d800100002359 0x0000000000000000
// isa & ISA_MASK
(lldb) po 0x001d800100002359 & 0x00007ffffffffff8ULL
PYTeacher
从上面的打印结果,我们得出 teacher
对象的 isa
指向 PYTeacher
类:
类的 isa
指向元类
由第一节我们得出,类也是对象,也存在isa指针,那类的isa指针指向哪里呢?
我们打印一下类的isa
:
(lldb) x/2gx PYTeacher.class
0x100002358: 0x0000000100002330 0x00007fff940b3118
// 打印 PYTeacher 的元类
(lldb) po 0x0000000100002330 & 0x00007ffffffffff8ULL
PYTeacher
从打印结果,我们看到,PYTeacher
的isa
也指向PYTeacher
,那这两个PYTeacher
类是同一个吗?我们打印一下他们各自的地址:
(lldb) p/x 0x0000000100002330 & 0x00007ffffffffff8ULL
// PYTeacher元类的地址
(unsigned long long) $4 = 0x0000000100002330
(lldb) p/x PYTeacher.class
// PYTeacher类的地址
(Class) $5 = 0x0000000100002358 PYTeacher
我们看到这两个PYTeacher
的地址并不相同,实际上PYTeacher
的isa
指向了他的元类 PYTeacher
:
元类的 isa
指向根元类
得到上面isa
的指向流程以后,我们肯定会想,元类的 isa 指向谁呢?
我们还是通过打印来看一下:
(lldb) x/2gx 0x0000000100002330 // PYTeacher元类的地址
0x100002330: 0x00007fff940b30f0 0x00007fff940b30f0
// 打印根元类
(lldb) po 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
NSObject
从打印结果看,元类的isa
指向NSObject
,这时,我们会想这个NSObject
是我们常用的那个NSObject
吗?我们来验证一下:
(lldb) p/x 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
(unsigned long long) $7 = 0x00007fff940b30f0 // 刚才得出的NSObject的地址
(lldb) p/x NSObject.class
(Class) $8 = 0x00007fff940b3118 NSObject // 我们常用的NSObject的地址
(lldb) x/2gx NSObject.class
0x7fff940b3118: 0x00007fff940b30f0 0x0000000000000000
(lldb) p/x 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
(unsigned long long) $10 = 0x00007fff940b30f0 // NSObject的元类的地址
我们看到刚才得出的NSObject
是NSObject
的元类
,也就是根元类
:
根元类的 isa
指向自己
验证到这里,肯定有人会想,根元类的 isa 又指向哪里呢?
我们继续验证:
(lldb) x/2gx 0x00007fff940b30f0 // 根元类的地址
0x7fff940b30f0: 0x00007fff940b30f0 0x00007fff940b3118
(lldb) p/x 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
(unsigned long long) $11 = 0x00007fff940b30f0 // 根元类isa指向的地址
(lldb) po 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
NSObject
打印结果得出:根元类的 isa
指向了自己。
类的结构
我们在iOS的源码中看到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();
}
省略。。。
}
objc_class
的成员变量在内存中的分布如下:
从内存分布可以看出,我们可以通过移动
pointer
(类的地址指针),来读取不同地址的信息。我们知道 isa
是 8
个字节,所以对 pointer
移动8
个字节,就可以读取superclass
的内容,下面我们可以验证一下:
@interface PYEnglishTeacher : PYTeacher
@end
@implementation PYEnglishTeacher
@end
我们创建了一个PYEnglishTeacher
类继承自PYTeacher
类:
(lldb) p/x PYEnglishTeacher.class
(Class) $0 = 0x0000000100002438 PYEnglishTeacher
(lldb) po 0x0000000100002438 + 8
<PYTeacher: 0x100002440>
所以,对PYEnglishTeacher
类的地址移动8
个字节,就可以读取到superclass
。
Methods
我们发现在objc_class
中,有isa
、superclass
、cache
、bits
等成员变量,但是我们自己声明的属性、方法、协议存储在哪呢?我们知道,在以前的objc_class
定义中,是有objc_ivar_list
、objc_method_list
、objc_cache
、objc_protocol_list
等成员变量的,那现在苹果是如何设计的呢?我们发现objc_class
中有class_rw_t *data()
的方法,class_rw_t
定义如下:
struct class_rw_t {
省略。。。
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>()->ro;
}
return v.get<const class_ro_t *>();
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>()->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
};
从 class_rw_t
的结构,我们得出可以通过 class_rw_t
来读取方法列表等。所以首先我们要知道,需要移动多少字节,才能读取objc_class
中的bits
。
我们知道isa
为8字节,superclass
为8字节,那cache
是多少字节呢?我们看一下cache_t
的结构:
struct cache_t {
explicit_atomic<struct bucket_t *> _buckets; // 8字节
explicit_atomic<mask_t> _mask; // 4字节
省略。。
#if __LP64__
uint16_t _flags; // 2字节
#endif
uint16_t _occupied; // 2字节
}
所以 cache
是16字节。所以pointer
移动32个字节就是bits
的地址。我们打印一下PYTeacher
:
(lldb) p/x PYTeacher.class
(Class) $0 = 0x0000000100002208 PYTeacher
// 打印class_data_bits_t bits地址
(lldb) p (class_data_bits_t *)(0x0000000100002208 + 32)
(class_data_bits_t *) $1 = 0x0000000100002228
$1
为class_data_bits_t
结构体的指针,通过调用data()
方法可以得到class_rw_t
的指针:
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001021100b0
我们打印$2
指向的class_rw_t
结构体:
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975600
}
firstSubclass = PYEnglishTeacher
nextSiblingClass = NSUUID
}
现在我们PYTeacher
添加一些方法和属性:
@interface PYTeacher : NSObject {
NSString *name;
}
@property (nonatomic, assign) int age;
- (void)teach;
+ (void)study;
@end
@implementation PYTeacher
- (void)teach {
}
+ (void)study {
}
@end
这时,我们再打印class_rw_t
的内容:
(lldb) p/x PYTeacher.class
(Class) $0 = 0x0000000100002300 PYTeacher
// 打印bits的地址
(lldb) p (class_data_bits_t *)(0x0000000100002300 + 32)
(class_data_bits_t *) $1 = 0x0000000100002320
// 获取class_rw_t的指针
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100677d80
// 打印 class_rw_t 内容
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975632
}
firstSubclass = PYEnglishTeacher
nextSiblingClass = NSUUID
}
// 获取class_rw_t中的方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020d8
arrayAndFlag = 4294975704
}
}
}
// 获取method_list_t的数组指针
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000020d8
// 打印method_list_t数组,其实就是数组的第0位
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "teach"
types = 0x0000000100000f74 "v16@0:8"
imp = 0x0000000100000de0 (KCObjc`-[PYTeacher teach] at main.m:24)
}
}
}
// 打印数组中的内容
(lldb) p $6.get(0)
(method_t) $7 = {
name = "teach"
types = 0x0000000100000f74 "v16@0:8"
imp = 0x0000000100000de0 (KCObjc`-[PYTeacher teach] at main.m:24)
}
(lldb) p $6.get(1)
(method_t) $8 = {
name = ".cxx_destruct"
types = 0x0000000100000f74 "v16@0:8"
imp = 0x0000000100000e30 (KCObjc`-[PYTeacher .cxx_destruct] at main.m:22)
}
(lldb) p $6.get(2)
(method_t) $9 = {
name = "age"
types = 0x0000000100000f8a "i16@0:8"
imp = 0x0000000100000df0 (KCObjc`-[PYTeacher age] at main.m:15)
}
(lldb) p $6.get(3)
(method_t) $10 = {
name = "setAge:"
types = 0x0000000100000f92 "v20@0:8i16"
imp = 0x0000000100000e10 (KCObjc`-[PYTeacher setAge:] at main.m:15)
}
我们看到方法列表中有4
个方法,但是没有
+ (void)study
方法,为什么呢?
仔细观察我们会发现,这4个方法,除了1个C++
的方法外,其他3个都是对象方法
,也就是对象方法存在类的方法列表里
,我们说万物皆对象
,类
是元类
的对象
,难道说类对象
的”对象方法“
存在元类
的方法列表里?下面我们验证一下:
(lldb) x/2gx PYTeacher.class
0x100002300: 0x00000001000022d8 0x0000000100334140
(lldb) po 0x00000001000022d8 & 0x00007ffffffffff8ULL
PYTeacher
// 打印 PYTeacher元类 的地址
(lldb) p/x 0x00000001000022d8 & 0x00007ffffffffff8ULL
(unsigned long long) $13 = 0x00000001000022d8
// 打印元类中的 bits
(lldb) p (class_data_bits_t *)(0x00000001000022d8 + 32)
(class_data_bits_t *) $14 = 0x00000001000022f8
// 获取 class_rw_t 的地址
(lldb) p $14->data()
(class_rw_t *) $15 = 0x0000000100677d40
// 打印 class_rw_t 的内容
(lldb) p *$15
(class_rw_t) $16 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975528
}
firstSubclass = 0x0000000100002328
nextSiblingClass = 0x00007fff8b968cd8
}
// 获取 元类 的方法列表
(lldb) p $16.methods()
(const method_array_t) $17 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002070
arrayAndFlag = 4294975600
}
}
}
// 打印方法列表的地址
(lldb) p $17.list
(method_list_t *const) $18 = 0x0000000100002070
// 打印方法列表信息
(lldb) p *$18
(method_list_t) $19 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "study"
types = 0x0000000100000f74 "v16@0:8"
imp = 0x0000000100000dd0 (KCObjc`+[PYTeacher study] at main.m:28)
}
}
}
正如我们猜测的那样,+ (void)study
方法存在元类
的方法列表
中。
Properties
我们再来看一下property
:
(lldb) p $3.properties()
(const property_array_t) $20 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002188
arrayAndFlag = 4294975880
}
}
}
// 打印属性列表地址
(lldb) p $20.list
(property_list_t *const) $21 = 0x0000000100002188
// 打印属性列表信息
(lldb) p *$21
(property_list_t) $22 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "age", attributes = "Ti,N,V_age")
}
}
这里我们看到了age
属性,在class_rw_t
中,我们却没找到成员变量
的存储位置,但是class_rw_t
中,有获取和设置class_ro_t
的方法,我们来看一下class_ro_t
结构体。
class_ro_t
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;
省略。。。
};
我们看到class_ro_t
中有const ivar_list_t * ivars
,我们来打印一下:
(lldb) p $3.ro()
(const class_ro_t *) $23 = 0x0000000100002090
// 打印 class_ro_t 内容
(lldb) p *$23
(const class_ro_t) $24 = {
flags = 388
instanceStart = 8
instanceSize = 20
reserved = 0
ivarLayout = 0x0000000100000f28 "\x01"
name = 0x0000000100000f1e "PYTeacher"
baseMethodList = 0x00000001000020d8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100002140
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100002188
_swiftMetadataInitializer_NEVER_USE = {}
}
// 打印 ivar_list_t 地址
(lldb) p $24->ivars
(const ivar_list_t *const) $25 = 0x0000000100002140
Fix-it applied, fixed expression was:
$24.ivars
// 打印 ivar_list_t 内容
(lldb) p *$25
(const ivar_list_t) $26 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000022c8
name = 0x0000000100000f4a "name"
type = 0x0000000100000f7c "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
// 获取成员变量
(lldb) p $26.get(0)
(ivar_t) $27 = {
offset = 0x00000001000022c8
name = 0x0000000100000f4a "name"
type = 0x0000000100000f7c "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $26.get(1)
(ivar_t) $28 = {
offset = 0x00000001000022d0
name = 0x0000000100000f4f "_age"
type = 0x0000000100000f88 "i"
alignment_raw = 2
size = 4
}
ivar_list_t
中存储这name
和_age
成员变量。
小结
- 对象的
成员变量
存储在类
的class_ro_t
中; -
对象方法
和属性
存储在类
的class_rw_t
中; -
类方法
存储在元类
的class_rw_t
中。