前言
在前面的文章中我們知道在對象的isa指針中存儲了類的信息嗤瞎,也知道了Class = isa & ISA_MASK墙歪,今天我們來探索一下類以及元類的繼承鏈與類的數(shù)據(jù)結(jié)構(gòu)。
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指向當(dāng)前類贝奇,當(dāng)前類的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
從打印結(jié)果可以得到上面結(jié)論
- 任何類的元類繼承它父類的元類该镣,父類的元類繼承根元類冻璃,根元類繼承根類
配啥說perMeteSupClass是NSOject元類,perMeteSupSupClass是NSOject類呢?
從打印結(jié)果可以得到上面結(jié)論是正確的
2.類的結(jié)構(gòu)
從底層源碼可知道Class的本質(zhì)是一個叫objc_class的結(jié)構(gòu)體指針
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數(shù)據(jù)
從類結(jié)構(gòu)代碼可以看到objc_class繼承objc_object結(jié)構(gòu)體,private繼承objc_object的成員變量isa
struct objc_class : objc_object {
// Class ISA; //繼承至objc_object的isa指針 8個字節(jié)
Class superclass; //結(jié)構(gòu)體指針8個字節(jié)
cache_t cache; //16個字節(jié) // formerly cache pointer and vtable
class_data_bits_t bits; //類的地址偏移32位省艳,就可以得到bits
}
為啥說cache是16字節(jié)呢歌粥?
struct cache_t {
private:
//typedef unsigned long uintptr_t; 占用8字節(jié)
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask; //typedef uint32_t mask_t; 4個字節(jié)
#if __LP64__
uint16_t _flags; //2個字節(jié)
#endif
uint16_t _occupied; //2個字節(jié)
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;//8個字節(jié)
};
}
所以cache是16字節(jié),類的isa指針偏移32字節(jié)就可以得到bits數(shù)據(jù)
3.2 class_rw_t結(jié)構(gòu)探索
從上面的bits數(shù)據(jù)中我們可以得到一個class_rw_t數(shù)據(jù)拍埠,class_rw_t在2020的wwdc大會的runtime視頻有講到失驶,截圖如下。里面包含了方法枣购、屬性嬉探、協(xié)議列表等
并且查看class_rw_t結(jié)構(gòu)體源碼,主要有以下幾個屬性
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<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
}
firstSubClass與nextSiblingClass
:所有的類都會鏈接成一個樹狀結(jié)構(gòu)棉圈,這是通過使用first subclass和Next Sibling Class指針實現(xiàn)的涩堤,這允許運行時遍歷當(dāng)前使用的所有類。
Demangled Name
:swift類會使用demangled name字段分瘾,并且swift類不需要這個字段胎围,除非有東西詢問它們的objective-c名稱時才需要
ro_or_rw_ext
:存儲class_ro_t或者class_rw_ext_t信息,下面這張圖片是從蘋果2020wwdc視頻中扒取的
通過查看class_rw_t結(jié)構(gòu)體源碼德召,可以發(fā)現(xiàn)它提供了獲取方法白魂、屬性、協(xié)議等方法
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 *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
下面我們通過提供的方法嘗試獲取一下它的方法上岗、屬性福荸、協(xié)議如下
3.2.1 lldb獲取類方法列表
(lldb) p/x ZFPerson.class //獲取類地址
(Class) $0 = 0x0000000100002850 ZFPerson
(lldb) p/x 0x0000000100002850 + 0x20 //地址偏移32位獲取bits數(shù)據(jù)
(long) $1 = 0x0000000100002870
(lldb) p (class_data_bits_t*)0x0000000100002870 //強制轉(zhuǎn)換bits為class_data_bits_t指針
(class_data_bits_t *) $2 = 0x0000000100002870
(lldb) p $2->data() //獲取class_rw_t 數(shù)據(jù),data方法是結(jié)構(gòu)體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<unsigned long> = 4294976080
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $4.methods() //獲取方法列表
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
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<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (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中的結(jié)構(gòu)以init方法舉例
name = "init" //方法的名稱(sel),選擇器是字符串,它具有唯一性呆瞻,所以它們可以使用指針相等來進行比較
types = 0x0000000100001ecf "@16@0:8" //類型編碼:表示參數(shù)和返回類型的字符串台夺,它不是用來發(fā)送消息的,但它是運行時introspection和消息forwarding所必需的
//指向方法的實現(xiàn)的指針
imp = 0x0000000100001aa0 (ZFObjcBuild`-[ZFPerson init])
從上面可以看到我們輸出的方法列表中是沒有類方法痴脾,因為類方法是存儲在類的元類中
在ZFPerson聲明一個類方法sleep,驗證的具體步驟我就不寫了颤介,跟上面獲取lldb差不多,只是用類的元類地址去做偏移
為什么類方法會放在元類中呢明郭?這是為了避免與同名的對象方法產(chǎn)生沖突买窟,畢竟方法在底層存儲方式就是sel與imp
3.2.2 lldb獲取類的屬性列表
3.2.3 lldb獲取類的協(xié)議列表
聲明了一個ZFPersonDelegate協(xié)議,讓ZFPerson遵循它
@protocol ZFPersonDelegate<NSObject>
- (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<unsigned long> = 4294976080
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $17.protocols()
(const protocol_array_t) $18 = {
list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100002348
}
arrayAndFlag = 4294976328
}
}
}
(lldb) p $18.list
(RawPtr<protocol_list_t>) $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結(jié)構(gòu)中的protocol信息
4.補充
4.1 類型編碼
獲取方法的類型編碼 method_getTypeEncoding