在上一篇中,我們對isa的初始化、類與對象的底層結(jié)構(gòu)以及屬性進行了簡單剝析。
- 對于
isa,我們得出結(jié)論,isa是一個存儲了所屬類的地址; - 對于類的底層結(jié)構(gòu),我們得出了類有四個成員,
isa、superclass、cache、bits - 對于屬性,我們得出了屬性是由
實例變量+setter+getter組成。
在這一篇中,我們將進一步探析:
-
isa指向的類的isa指向什么呢,如果一直指向下去,最終會指向哪里? - 對于
cache、bits,它們分別是用來做什么的呢?它們內(nèi)部結(jié)構(gòu)是什么樣的呢?
我們接下來逐一進行分析
一、isa指向
1.使用lldb命令分析isa指向
我們在main.m中,首先創(chuàng)建一個LWPerson類繼承自NSObject,然后再創(chuàng)建一個LWStudent繼承自LWPerson,并在main函數(shù)中創(chuàng)建兩個類的實例,在創(chuàng)建后打一個斷點,如下圖所示:
在上一篇中我們知道,isa.bits & ISA_MASK就可以獲得isa指向的類的地址,據(jù)此,我們使用lldb來調(diào)試查找isa的走位,如下面代碼所示
//16進制打印student的地址
(lldb) p/x student
(LWStudent *) $0 = 0x0000000100546690
//根據(jù)student的內(nèi)容連續(xù)打印四段,第一段就是isa.bits
(lldb) x/4gx $0
0x100546690: 0x001d8001000023d5 0x00000000003c0001
0x1005466a0: 0x0000000100001030 0x0000000000000457
//得到isa指向的類的地址
(lldb) p/x 0x001d8001000023d5 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x00000001000023d0
//po之后,可以看到指向LWStudent
(lldb) po $1
LWStudent
//繼續(xù)找LWStudent的isa指向
(lldb) x/4gx $1
0x1000023d0: 0x00000001000023a8 0x0000000100002380
0x1000023e0: 0x00000001005466f0 0x0002802c00000007
(lldb) p/x 0x00000001000023a8 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000023a8
//po 之后發(fā)現(xiàn),也是指向LWStudent
(lldb) po $2
LWStudent
//繼續(xù)查找isa指向
(lldb) x/4gx $2
0x1000023a8: 0x00007fff991e60f0 0x0000000100002358
0x1000023b8: 0x0000000100546770 0x0003e03500000007
(lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x00007fff991e60f0
//此處發(fā)現(xiàn)指向NSObject
(lldb) po $3
NSObject
//繼續(xù)查找NSObject的isa指向
(lldb) x/4gx $3
0x7fff991e60f0: 0x00007fff991e60f0 0x00007fff991e6118
0x7fff991e6100: 0x0000000100704fb0 0x0004e03100000007
(lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x00007fff991e60f0
(lldb) po $4
//發(fā)現(xiàn),NSObject的isa還是指向NSObject
NSObject
//我們繼續(xù)查找
(lldb) x/4gx $4
0x7fff991e60f0: 0x00007fff991e60f0 0x00007fff991e6118
0x7fff991e6100: 0x0000000100704fb0 0x0004e03100000007
(lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
(unsigned long long) $5 = 0x00007fff991e60f0
(lldb) po $5
//發(fā)現(xiàn)仍然是NSObject,并且地址都是0x00007fff991e60f0
NSObject
上方的lldb指令大致流程就是從查找student的isa指向出發(fā),不斷的查找指向的指向,其大致流程圖如下
[圖片上傳失敗...(image-e690ab-1600595328194)]
從案例中我們可以發(fā)現(xiàn),對象student的isa指向Student,而Student的isa也是指向的Student,Student的isa指向NSObject,而NSObject的isa指向自己。
在這里,連續(xù)出現(xiàn)了兩個Student,這兩個的地址相差40個字節(jié),并不是同一個,為什么會出現(xiàn)兩個不同的Student呢?
為了分析這個問題,我們使用lldb命令,新創(chuàng)建一個NSObject類的對象,繼續(xù)查找是否會出現(xiàn)兩個NSObject,如下所示
//創(chuàng)建一個NSObject的對象
(lldb) expr [NSObject alloc]
(NSObject *) $6 = 0x000000010071b990
(lldb) po $6
<NSObject: 0x10071b990>
(lldb) x/4gx $6
0x10071b990: 0x001dffff991e6119 0x0000000000000000
0x10071b9a0: 0x656b6f54534e5b2d 0x7420646c6569466e
(lldb) p/x 0x001dffff991e6119 & 0x00007ffffffffff8ULL
//找到它的指向,地址為0x00007fff991e6118
(unsigned long long) $7 = 0x00007fff991e6118
(lldb) po $7
NSObject
//繼續(xù)查找NSObject的isa指向
(lldb) x/4gx $7
0x7fff991e6118: 0x00007fff991e60f0 0x0000000000000000
0x7fff991e6128: 0x0000000100630340 0x0004801000000007
(lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
//找到NSObject的isa指向,地址為0x00007fff991e60f0
(unsigned long long) $8 = 0x00007fff991e60f0
(lldb) po $8
NSObject
測試后可以看出,NSObject也有兩個,兩者的地址相差也是40個字節(jié),其最終指向的NSObject的地址為0x00007fff991e60f0,與我們之前指令得到的最終指向NSObject的地址相同。
2.通過調(diào)用runtime API來分析isa走向
我們在main函數(shù)中編輯代碼如下:
int main(int argc, const char * argv[]) {
LWStudent *student = [LWStudent alloc];
Class class1 = object_getClass(student);
NSLog(@"class1 is %@",class1);
Class class2 = object_getClass(class1);
NSLog(@"class2 is %@",class2);
Class class3 = object_getClass(class2);
NSLog(@"class3 is %@",class3);
Class class4 = object_getClass(class3);
NSLog(@"class4 is %@",class4);
Class class5 = object_getClass(class4);
NSLog(@"class5 is %@",class5);
return 0;
}
//打印結(jié)果
class1 is LWStudent
class2 is LWStudent
class3 is NSObject
class4 is NSObject
class5 is NSObject
結(jié)果與lldb調(diào)試得到的結(jié)果一致
3.isa指向圖、類對象、元類
綜合以上分析,我們得到了這張經(jīng)典圖
在之前的測試中,我們得到兩個類分別稱之為類對象與元類,類對象是元類的對象,而NSObject的元類我們稱之為根元類。
這時,isa的指向我們可以總結(jié)為:對象->類對象->元類->根元類->根元類。根元類繼承于NSObject,它的isa指向自身。
4.分析類結(jié)構(gòu)體struct objc_class結(jié)構(gòu)
同時,根據(jù)我們之前的測試,類對象的地址比它的元類高40字節(jié),我們根據(jù)struct objc_class的結(jié)構(gòu)進行分析。
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();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
}
其中cache_t是一個結(jié)構(gòu)體,它內(nèi)部所有的成員變量如下:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#else
#error Unknown cache mask storage type.
#endif
uint16_t _flags;
uint16_t _occupied;
}
其中,uintptr_t是unsigned long的別名,占8個字節(jié);mask_t是uint_32_t的別名,占4個字節(jié)。
另外,我們看到,cache_t內(nèi)部針對不同平臺使用了不同的存儲結(jié)構(gòu),我們可以在objc-runtime-new.h中看到對于各個存儲類型的定義
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#if defined(__arm64__) && __LP64__
//arm64架構(gòu)且是64位Unix系統(tǒng),也就是我們現(xiàn)在的iOS設(shè)備
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
//arm64架構(gòu)但不是64位Unix系統(tǒng),iphone 5s之前的設(shè)備都是32位
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
//其它,MAC_OS設(shè)備
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
回到我們的cache_t結(jié)構(gòu)體中,無論是哪種平臺,根據(jù)內(nèi)存對齊規(guī)則,它需要的存儲空間都是16字節(jié)。
我們再看class_data_bits_t的結(jié)構(gòu),它內(nèi)部的結(jié)構(gòu)比較簡單
struct class_data_bits_t {
uintptr_t bits;
}
所以,class_data_bits_t bits占8字節(jié)。
綜上所述,struct objc_class所占的內(nèi)存如下圖所示,正好是40個字節(jié)

所以,我們得出結(jié)論,在內(nèi)存中,類對象與元類的存儲是緊密挨在一起的。
二、class_data_bits_t
接下來我們分析class_data_bits_t,在objc781源碼中,class_data_bits_t和以前有了很大的改變,現(xiàn)在的源碼中,它只有一個成員bits。
想要獲取
class_rw_t,可以通過struct objc_class的常成員函數(shù)class_rw_t* data()來獲取;或者通過地址偏移先獲得class_data_bits_t,再通過它的常成員函數(shù)class_rw_t* data()來獲取。想要獲取
class_ro_t,可以通過struct class_data_bits_t的成員方法const class_ro_t *safe_ro()獲取;或者在獲取到class_rw_t的數(shù)據(jù)后,使用struct class_rw_t的常成員函數(shù)class_ro_t *ro()來獲取。
1.地址偏移
在測試class_data_bits_t之前,我們先介紹一下什么是地址偏移。
有如下C語言的代碼:
int a[] = {0,1,2,3,4,5,6,7,8,9};
int temp1 = a[4];
int temp2 = *(a+4);
我們知道,C語言中,數(shù)組名就是指向數(shù)組首地址的指針,我們在獲取數(shù)組元素的時候,可以使用下標獲取a[4],也可以通過指針偏移的方式*(a+4),因為a有被定義為了int類型,所以,加一個偏移量就是偏移一個int的字節(jié)大小,在上例中,a[4]和*(a+4)獲取到的值相同,都是獲取數(shù)組第5個元素的值。
在內(nèi)存中,我們要讀取內(nèi)存中的值也可以使用這樣的方式,由于地址是沒有類型的,所以我們的偏移量需要直接設(shè)置為多少字節(jié)。
如我們有了struct objc_class的指針,根據(jù)我們之前對它內(nèi)部結(jié)構(gòu)的分析,我們獲取它指向的內(nèi)容地址后,可以如下得到各個成員的地址:
-
前面8個字節(jié)就是isa -
偏移8個字節(jié)可以得到父類指針superclass -
偏移16個字節(jié)可以得到緩存cache -
偏移32個字節(jié)可以得到class_data_bits_t類型的bits數(shù)據(jù)
下面我們根據(jù)這個思想使用lldb找到class_data_bits_t,并且分析它內(nèi)部的成員。
2.lldb分析class_data_bits_t
1).設(shè)置調(diào)試案例
我們打開可編譯的源碼,在main.m中添加代碼如下:
@interface LWPerson : NSObject{
NSInteger a;
NSString *b;
CGFloat c;
}
//姓名
@property (nonatomic,copy) NSString *name;
//年齡
@property (nonatomic,assign) short age;
//性別
@property (nonatomic,assign) BOOL isMan;
@end
@implementation LWPerson
- (void)sayHello{
NSLog(@"%s",__func__);
}
- (void)sayByeBye{
NSLog(@"%s",__func__);
}
+ (void)eat{
NSLog(@"%s",__func__);
}
+ (void)drink{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
LWPerson *person = [[LWPerson alloc]init];
person.name = @"Jobs";
person.age = 77;
person.isMan = YES;
[person sayHello];
[person sayByeBye];
[LWPerson eat];
[LWPerson drink];
return 0;
}
2).lldb調(diào)試查看class_rw_t結(jié)構(gòu)
我們在[LWPerson drink];前添加一個斷點進行調(diào)試。
//獲取person的類LWPerson的地址
(lldb) p/x person.class
(Class) $0 = 0x0000000100002368 LWPerson
//地址偏移32個字節(jié)獲取class_data_bits_t,16進制下就是加上20
(lldb) p (class_data_bits_t *)0x0000000100002388
(class_data_bits_t *) $1 = 0x0000000100002388
//調(diào)用class_data_bits_t的成員方法data()獲取到class_rw_t的指針
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100655680
(lldb) p *$2
//查看class_rw_t的內(nèi)容
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975656
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
3).class_ro_t、class_rw_t
關(guān)于class_ro_t、class_rw_t的探索過程我們以后再去探究,關(guān)于這兩個結(jié)構(gòu)體,我們先大致解釋一下:
-
class_ro_t是編譯期生成的,它存儲了當前類在編譯期就已經(jīng)確定的屬性、方法以及協(xié)議,它里面是沒有分類中定義的方法和協(xié)議的。 -
class_rw_t是在運行時生成的,它在realizeClass中生成,它包含了class_ro_t。它在_objc_init方法中關(guān)于dyld的回調(diào)的map_images中最終將分類的方法與協(xié)議都插入到自己的方法列表、協(xié)議列表中。它不包含成員變量列表,因為成員變量列表是在編譯期就確定好的,它只保存在class_ro_t中。不過,class_rw_t中包含了一個指向class_ro_t的指針。
在class_rw_t的類結(jié)構(gòu)中,有如下一些成員函數(shù):
struct class_rw_t {
public:
//獲取class_ro_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 *>();
}
//設(shè)置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};
}
}
//獲取協(xié)議列表
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};
}
}
};
根據(jù)這些暴露出來的方法,我們繼續(xù)使用lldb查看class_rw_t的內(nèi)容。
4).尋找實例方法
我們繼續(xù)進行lldb調(diào)試,先看看有哪些方法
//獲取方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020f0
arrayAndFlag = 4294975728
}
}
}
//讀取實際的方法列表(list)
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000020f0
//讀取list內(nèi)部的結(jié)構(gòu)
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 9
first = {
name = "sayHello"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b90 (LWObjc`-[LWPerson sayHello] at main.m:27)
}
}
}
//獲取第一個方法,看得出來是- (void)sayHello
(lldb) p $6.get(0)
(method_t) $7 = {
name = "sayHello"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b90 (LWObjc`-[LWPerson sayHello] at main.m:27)
}
//獲取第二個方法,看得出來是- (void)sayByeBye
(lldb) p $6.get(1)
(method_t) $8 = {
name = "sayByeBye"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000bc0 (LWObjc`-[LWPerson sayByeBye] at main.m:30)
}
////獲取第三個方法,看得出來是isMan的getter方法
(lldb) p $6.get(2)
(method_t) $9 = {
name = "isMan"
types = 0x0000000100000efd "c16@0:8"
imp = 0x0000000100000c90 (LWObjc`-[LWPerson isMan] at main.m:21)
}
//獲取第四個方法,看得出來是isMan的setter方法
(lldb) p $6.get(3)
(method_t) $10 = {
name = "setIsMan:"
types = 0x0000000100000f05 "v20@0:8c16"
imp = 0x0000000100000cb0 (LWObjc`-[LWPerson setIsMan:] at main.m:21)
}
//獲取第五個方法,看得出來是c++析構(gòu)方法
(lldb) p $6.get(4)
(method_t) $11 = {
name = ".cxx_destruct"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000cd0 (LWObjc`-[LWPerson .cxx_destruct] at main.m:25)
}
//獲取第六個方法,看得出來是name的getter方法
(lldb) p $6.get(5)
(method_t) $12 = {
name = "name"
types = 0x0000000100000ed7 "@16@0:8"
imp = 0x0000000100000bf0 (LWObjc`-[LWPerson name] at main.m:17)
}
//獲取第七個方法,看得出來是name的setter方法
(lldb) p $6.get(6)
(method_t) $13 = {
name = "setName:"
types = 0x0000000100000edf "v24@0:8@16"
imp = 0x0000000100000c20 (LWObjc`-[LWPerson setName:] at main.m:17)
}
//獲取第八個方法,看得出來是age的getter方法
(lldb) p $6.get(7)
(method_t) $14 = {
name = "age"
types = 0x0000000100000eea "s16@0:8"
imp = 0x0000000100000c50 (LWObjc`-[LWPerson age] at main.m:19)
}
//獲取第九個方法,看得出來是age的setter方法
(lldb) p $6.get(8)
(method_t) $15 = {
name = "setAge:"
types = 0x0000000100000ef2 "v20@0:8s16"
imp = 0x0000000100000c70 (LWObjc`-[LWPerson setAge:] at main.m:19)
}
//獲取第十個方法時提示越界
(lldb) p $6.get(9)
Assertion failed: (i < count), function get, file .../runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
從結(jié)果我們可以看出,所有的實例方法和屬性的setter、getter方法都存放在類對象的class_rw_t的方法列表中。
方法types
v16@0:8的解釋:
v表示方法返回值是void,16表示方法所有參數(shù)一共占16字節(jié),由于所有OC方法有兩個默認的參數(shù)id self和SEL _cmd,@表示對象類型,也就是id self,0表示這個參數(shù)從0字節(jié)位置開始,:表示方法,也就是SEL _cmd,8表示這個參數(shù)從第8個字節(jié)位置開始
5).探索屬性列表
我們再看看屬性列表
//獲取屬性列表
(lldb) p $3.properties()
(const property_array_t) $16 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002298
arrayAndFlag = 4294976152
}
}
}
//讀取實際的屬性列表
(lldb) p $16.list
(property_list_t *const) $17 = 0x0000000100002298
//我們在讀取屬性列表的時候被打斷了,沒有權(quán)限
(lldb) p *$17
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
6).探索成員變量列表
在前面的我們提到,成員變量在class_ro_t中,而不在class_rw_t中,以為編譯結(jié)束類的結(jié)構(gòu)就已經(jīng)確定了,不能夠再被改變。
所以,我們先從class_rw_t獲取到class_ro_t,再查找ivars
//得到`class_ro_t`
(lldb) p $3.ro()
(const class_ro_t *) $19 = 0x00000001000020a8
//查看`class_ro_t`結(jié)構(gòu)
(lldb) p *$19
(const class_ro_t) $20 = {
flags = 388
instanceStart = 8
instanceSize = 48
reserved = 0
ivarLayout = 0x0000000100000e4b "\x11!"
name = 0x0000000100000e42 "LWPerson"
baseMethodList = 0x00000001000020f0
baseProtocols = 0x0000000000000000
ivars = 0x00000001000021d0
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100002298
_swiftMetadataInitializer_NEVER_USE = {}
}
//獲得ivars
(lldb) p $20.ivars
(const ivar_list_t *const) $21 = 0x00000001000021d0
//查看ivars
(lldb) p *$21
(const ivar_list_t) $22 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 6
first = {
offset = 0x0000000100002310
name = 0x0000000100000e58 "a"
type = 0x0000000100000ec3 "q"
alignment_raw = 3
size = 8
}
}
}
//獲取第一個成員變量,這個是a
(lldb) p $22.get(0)
(ivar_t) $23 = {
offset = 0x0000000100002310
name = 0x0000000100000e58 "a"
type = 0x0000000100000ec3 "q"
alignment_raw = 3
size = 8
}
//獲取第二個成員變量,這個是b
(lldb) p $22.get(1)
(ivar_t) $24 = {
offset = 0x0000000100002318
name = 0x0000000100000e5a "b"
type = 0x0000000100000ec5 "@\"NSString\""
alignment_raw = 3
size = 8
}
//獲取第三個成員變量,這個是c
(lldb) p $22.get(2)
(ivar_t) $25 = {
offset = 0x0000000100002320
name = 0x0000000100000e5c "c"
type = 0x0000000100000ed1 "d"
alignment_raw = 3
size = 8
}
//獲取第四個成員變量,這個是_isMan
(lldb) p $22.get(3)
(ivar_t) $26 = {
offset = 0x0000000100002328
name = 0x0000000100000e5e "_isMan"
type = 0x0000000100000ed3 "c"
alignment_raw = 0
size = 1
}
//獲取第五個成員變量,這個是_age
(lldb) p $22.get(4)
(ivar_t) $27 = {
offset = 0x0000000100002330
name = 0x0000000100000e65 "_age"
type = 0x0000000100000ed5 "s"
alignment_raw = 1
size = 2
}
//獲取第六個成員變量,這個是_name
(lldb) p $22.get(5)
(ivar_t) $28 = {
offset = 0x0000000100002338
name = 0x0000000100000e6a "_name"
type = 0x0000000100000ec5 "@\"NSString\""
alignment_raw = 3
size = 8
}
//獲取第七個報錯,提示我們越界了
(lldb) p $22.get(6)
Assertion failed: (i < count), function get, file .../runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
3.類方法存儲位置的探索
在上面的案例探索中,我們都沒有發(fā)現(xiàn)類方法+ (void)eat和+ (void)drink,這是為什么呢?
在第一部分中,我們談了isa指向的問題,在我們的方法查找的過程中,我們的實例方法存放在類對象中,這部分我們已經(jīng)驗證了。而類方法保存在元類的方法列表中。
我們接下來對此進行驗證
//得到LWPerson的元類
(lldb) p/x object_getClass(person.class)
(Class) $0 = 0x0000000100002340
(lldb) po $0
LWPerson
//加上32字節(jié),16進制下就是加20,得到class_data_bits_t
(lldb) p (class_data_bits_t *)0x0000000100002360
(class_data_bits_t *) $1 = 0x0000000100002360
//獲取class_rw_t
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010111a7c0
//查看class_rw_t結(jié)構(gòu)
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975528
}
firstSubclass = nil
nextSiblingClass = 0x00007fff8b25dcd8
}
//獲取方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002070
arrayAndFlag = 4294975600
}
}
}
//獲取實例的方法列表list
(lldb) p $4.list
(method_list_t *const) $5 = 0x0000000100002070
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "eat"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b30 (KCObjc`+[LWPerson eat] at main.m:33)
}
}
}
//獲取第一個方法,我們發(fā)現(xiàn)是類方法eat
(lldb) p $6.get(0)
(method_t) $7 = {
name = "eat"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b30 (KCObjc`+[LWPerson eat] at main.m:33)
}
//獲取第二個方法,我們發(fā)現(xiàn)是類方法drink
(lldb) p $6.get(1)
(method_t) $8 = {
name = "drink"
types = 0x0000000100000ebb "v16@0:8"
imp = 0x0000000100000b60 (KCObjc`+[LWPerson drink] at main.m:36)
}
//獲取第三個方法時提示越界了
(lldb) p $6.get(2)
Assertion failed: (i < count), function get, file .../runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
這就是驗證了類方法存放在元類的方法列表中。
三、總結(jié)
在本篇中,我們研究了isa的走向以及對class_data_bits_t的結(jié)構(gòu)進行了分析。
isa的走向為:對象->類對象->元類->根元類->根元類。根元類繼承于NSObject,它的isa指向自身。class_data_bits_t中只有一個bits,它通過算法可以得到class_rw_t,class_rw_t存儲了了類的方法列表、屬性列表、協(xié)議列表;而成員變量存放在class_ro_t中。實例方法存放在類對象的方法列表中,類方法存放在元類的方法列表中。
下一篇我們繼續(xù)分析關(guān)于類結(jié)構(gòu)中的cache_t的作用于內(nèi)部結(jié)構(gòu)與算法。