前言
在前面的文章中我们知道在对象的isa指针中存储了类的信息,也知道了Class = isa & ISA_MASK,今天我们来探索一下类以及元类的继承链与类的数据结构。
ISA_MASK(掩码)
# if __arm64__
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# else
# define ISA_MASK 0x0000000ffffffff8ULL
elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
1.指针isa指向流程与类的继承链
1.1 isa指向流程
任何一个对象的isa指向当前类,当前类的isa指向元类,元类的isa指向NSObject(根元类),根元类的isa指向根元类。如下图所示
验证一下,建立一个ZFPerson类利用llvm打印对象的isa指针指向流程
1.2 类继承链
- 任何类继承它的父类,父类继承根类(大部分都是NSObject),根类继承一个nil,
验证一下,建立两个类ZFStudent与ZFPerson。
@interface ZFPerson:NSObject
@end
@implementation ZFPerson
@end
@interface ZFStudent:ZFPerson
@end
@implementation ZFStudent
@end
从打印结果可以得到上面结论
- 任何类的元类继承它父类的元类,父类的元类继承根元类,根元类继承根类
配啥说perMeteSupClass是NSOject元类,perMeteSupSupClass是NSOject类呢?
从打印结果可以得到上面结论是正确的
2.类的结构
从底层源码可知道Class的本质是一个叫objc_class的结构体指针
typedef struct objc_class *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
.....
}
对于着下面这一张图
3.类的bits探索
3.1获取bits数据
从类结构代码可以看到objc_class继承objc_object结构体,private继承objc_object的成员变量isa
struct objc_class : objc_object {
// Class ISA; //继承至objc_object的isa指针 8个字节
Class superclass; //结构体指针8个字节
cache_t cache; //16个字节 // formerly cache pointer and vtable
class_data_bits_t bits; //类的地址偏移32位,就可以得到bits
}
为啥说cache是16字节呢?
struct cache_t {
private:
//typedef unsigned long uintptr_t; 占用8字节
explicit_atomic _bucketsAndMaybeMask;
union {
struct {
explicit_atomic _maybeMask; //typedef uint32_t mask_t; 4个字节
#if __LP64__
uint16_t _flags; //2个字节
#endif
uint16_t _occupied; //2个字节
};
explicit_atomic _originalPreoptCache;//8个字节
};
}
所以cache是16字节,类的isa指针偏移32字节就可以得到bits数据
3.2 class_rw_t结构探索
从上面的bits数据中我们可以得到一个class_rw_t数据,class_rw_t在2020的wwdc大会的runtime视频有讲到,截图如下。里面包含了方法、属性、协议列表等
并且查看class_rw_t结构体源码,主要有以下几个属性
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
}
firstSubClass与nextSiblingClass
:所有的类都会链接成一个树状结构,这是通过使用first subclass和Next Sibling Class指针实现的,这允许运行时遍历当前使用的所有类。
Demangled Name
:swift类会使用demangled name字段,并且swift类不需要这个字段,除非有东西询问它们的objective-c名称时才需要
ro_or_rw_ext
:存储class_ro_t或者class_rw_ext_t信息,下面这张图片是从苹果2020wwdc视频中扒取的
通过查看class_rw_t结构体源码,可以发现它提供了获取方法、属性、协议等方法
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get(&ro_or_rw_ext)->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get(&ro_or_rw_ext)->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is()) {
return v.get(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get(&ro_or_rw_ext)->baseProtocols};
}
}
下面我们通过提供的方法尝试获取一下它的方法、属性、协议如下
3.2.1 lldb获取类方法列表
(lldb) p/x ZFPerson.class //获取类地址
(Class) $0 = 0x0000000100002850 ZFPerson
(lldb) p/x 0x0000000100002850 + 0x20 //地址偏移32位获取bits数据
(long) $1 = 0x0000000100002870
(lldb) p (class_data_bits_t*)0x0000000100002870 //强制转换bits为class_data_bits_t指针
(class_data_bits_t *) $2 = 0x0000000100002870
(lldb) p $2->data() //获取class_rw_t 数据,data方法是结构体class_data_bits_t提供的,具体可以看源码
(class_rw_t *) $3 = 0x000000010060e620
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic = 4294976080
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $4.methods() //获取方法列表
(const method_array_t) $5 = {
list_array_tt = {
= {
list = {
ptr = 0x0000000100002298
}
arrayAndFlag = 4294976152
}
}
}
(lldb) p $5.list.ptr
(method_list_t *const) $6 = 0x0000000100002298
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt = (entsizeAndFlags = 27, count = 7)
}
(lldb) p $7.get(0)
(method_t) $8 = {}
(lldb) p $7.get(2)
(method_t) $9 = {}
(lldb) p $7.get(1)
(method_t) $10 = {}
(lldb) p $7.get(0).big
(method_t::big) $11 = {
name = "killPig" //sel
types = 0x0000000100001ed7 "v16@0:8" //类型编码,
imp = 0x0000000100001b10 (ZFObjcBuild`-[ZFPerson killPig])
}
Fix-it applied, fixed expression was:
$7.get(0).big()
(lldb) p $7.get(1).big
(method_t::big) $12 = {
name = "eat"
types = 0x0000000100001ed7 "v16@0:8"
imp = 0x0000000100001b00 (ZFObjcBuild`-[ZFPerson eat])
}
Fix-it applied, fixed expression was:
$7.get(1).big()
(lldb) p $7.get(2).big
(method_t::big) $13 = {
name = "init"
types = 0x0000000100001ecf "@16@0:8"
imp = 0x0000000100001aa0 (ZFObjcBuild`-[ZFPerson init])
}
Fix-it applied, fixed expression was:
$7.get(2).big()
(lldb) p $7.get(3).big
(method_t::big) $14 = {
name = "name"
types = 0x0000000100001ecf "@16@0:8"
imp = 0x0000000100001b20 (ZFObjcBuild`-[ZFPerson name])
}
Fix-it applied, fixed expression was:
$7.get(3).big()
(lldb) p $7.get(4).big
(method_t::big) $15 = {
name = "setName:"
types = 0x0000000100001edf "v24@0:8@16"
imp = 0x0000000100001b50 (ZFObjcBuild`-[ZFPerson setName:])
}
Fix-it applied, fixed expression was:
$7.get(4).big()
(lldb) p $7.get(5).big
(method_t::big) $16 = {
name = "age"
types = 0x0000000100001f91 "i16@0:8"
imp = 0x0000000100001b80 (ZFObjcBuild`-[ZFPerson age])
}
Fix-it applied, fixed expression was:
$7.get(5).big()
(lldb) p $7.get(6).big
(method_t::big) $17 = {
name = "setAge:"
types = 0x0000000100001f99 "v20@0:8i16"
imp = 0x0000000100001ba0 (ZFObjcBuild`-[ZFPerson setAge:])
}
Fix-it applied, fixed expression was:
$7.get(6).big()
(lldb)
method_t中的结构以init方法举例
name = "init" //方法的名称(sel),选择器是字符串,它具有唯一性,所以它们可以使用指针相等来进行比较
types = 0x0000000100001ecf "@16@0:8" //类型编码:表示参数和返回类型的字符串,它不是用来发送消息的,但它是运行时introspection和消息forwarding所必需的
//指向方法的实现的指针
imp = 0x0000000100001aa0 (ZFObjcBuild`-[ZFPerson init])
从上面可以看到我们输出的方法列表中是没有类方法,因为类方法是存储在类的元类中
在ZFPerson声明一个类方法sleep,验证的具体步骤我就不写了,跟上面获取lldb差不多,只是用类的元类地址去做偏移
为什么类方法会放在元类中呢?这是为了避免与同名的对象方法产生冲突,毕竟方法在底层存储方式就是sel与imp
3.2.2 lldb获取类的属性列表
3.2.3 lldb获取类的协议列表
声明了一个ZFPersonDelegate协议,让ZFPerson遵循它
@protocol ZFPersonDelegate
- (void)killPig;
@end
(lldb) p/x ZFPerson.class
(Class) $13 = 0x0000000100002850 ZFPerson
(lldb) p 0x0000000100002850 + 0x20
(long) $14 = 4294977648
(lldb) p (class_data_bits_t*)4294977648
(class_data_bits_t *) $15 = 0x0000000100002870
(lldb) p $15->data()
(class_rw_t *) $16 = 0x0000000100627ba0
(lldb) p *$16
(class_rw_t) $17 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic = 4294976080
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $17.protocols()
(const protocol_array_t) $18 = {
list_array_tt = {
= {
list = {
ptr = 0x0000000100002348
}
arrayAndFlag = 4294976328
}
}
}
(lldb) p $18.list
(RawPtr) $19 = {
ptr = 0x0000000100002348
}
(lldb) p $19.ptr
(protocol_list_t *const) $20 = 0x0000000100002348
(lldb) p *$20
(protocol_list_t) $21 = (count = 1, list = protocol_ref_t [] @ 0x00007fd025b50828)
(lldb) p $21.list[0]
(protocol_ref_t) $22 = 4294977696
(lldb) p/x 4294977696
(long) $23 = 0x00000001000028a0
(lldb) p (protocol_t *)0x00000001000028a0
(protocol_t *) $24 = 0x00000001000028a0
(lldb) p *$24
(protocol_t) $25 = {
objc_object = {
isa = {
bits = 4298453192
cls = Protocol
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 537306649
magic = 0
weakly_referenced = 0
unused = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
}
mangledName = 0x0000000100001ea9 "ZFPersonDelegate"
protocols = 0x0000000100002430
instanceMethods = 0x0000000100002448
classMethods = 0x0000000000000000
optionalInstanceMethods = 0x0000000000000000
optionalClassMethods = 0x0000000000000000
instanceProperties = 0x0000000000000000
size = 96
flags = 0
_extendedMethodTypes = 0x0000000100002468
_demangledName = 0x0000000000000000
_classProperties = 0x0000000000000000
}
(lldb)
通过上面lldb步骤,输出了class_rw_t结构中的protocol信息
4.补充
4.1 类型编码
获取方法的类型编码 method_getTypeEncoding
苹果关于类型编码文档