我們在上一節(jié)isa的結(jié)構(gòu)分析分析了isa的結(jié)構(gòu),我們在創(chuàng)建一個類的時候打印其地址得到的第一個地址就是它的isa地址,我們知道所有的類都有一個isa指向它的本質(zhì),那么除了isa,類里面的結(jié)構(gòu)還有什么內(nèi)容呢?我們這一節(jié)就來分析類的結(jié)構(gòu)
實例對象、類、元類分析
在探究類的結(jié)構(gòu)之前我們先來分析一下實例對象,類和元類的關(guān)系
首先我們創(chuàng)建一個類如下:
#import <Foundation/Foundation.h>
#import "SYPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
SYPerson *person = [[SYPerson alloc]init];
NSLog(@"%p",person);
}
return 0;
}
注:
x/4gx: 以16進(jìn)制形式打印地址內(nèi)容,讀取4個16字節(jié)內(nèi)容
p/x: 打印變量的十六進(jìn)制格式信
po: 打印變量的 description 方法
通過isa & ISA_MSAK可以查看 isa 指向的類信息
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
...
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
我們通過打印分析isa走位

分析:
- 實例對象
person的isa指向了SYPerson類 - 類
SYPerson的isa指向了SYPerson元類 - 元類
SYPerson的isa指向了NSObject類 - 類
NSObject的isa指向了本身
那么得到的isa走位符合下圖
isa流程圖
查看類結(jié)構(gòu)源碼
要分析類SYPerson我們知道它繼承來自父類NSObject,所以我們通過查看NSObject的源碼找到了:
@protocol NSObject
- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
@property (readonly) Class superclass;
這里我們可以看到NSObject有一個Class類型superclass,再點進(jìn)去發(fā)現(xiàn)typedef struct objc_class *Class; 說明Class是一個名為objc_class的結(jié)構(gòu)體,也就是說類的本質(zhì)就是一個結(jié)構(gòu),那么我們想分析類結(jié)構(gòu)實質(zhì)上就是分析這個結(jié)構(gòu)體,我們可以看到結(jié)構(gòu)體objc_class的源碼發(fā)現(xiàn)有以下成員:
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
...
}
前面我們說過所有的類都有isa,為什么這里沒有呢?那是因為objc_class繼承來自objc_object,而objc_object里面已經(jīng)包含了isa
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
這里也說明所有以objc_object為模板創(chuàng)建的類都有isa。
通過地址平移分析objc_class結(jié)構(gòu)
- 地址平移
什么是地址平移呢?我們可以拿到類的地址后通過類的地址平移拿到里面的所有信息。通過下面例子我們可以了解一下地址平移
int a[4] = {1,2,3,4};
int *b = a;
NSLog(@"%p--%p--%p",&a,&a[0],&a[1]);
NSLog(@"%p--%p--%p",b,b+1,b+2);
for (int i = 0; i < 4; i++) {
int value = *(b + i);
NSLog(@"%d",value);
}
//打印結(jié)果
2020-09-13 15:54:53.066933+0800 KCObjc[61247:1111510] 0x7ffeefbff510--0x7ffeefbff510--0x7ffeefbff514
2020-09-13 15:54:53.067769+0800 KCObjc[61247:1111510] 0x7ffeefbff510--0x7ffeefbff514--0x7ffeefbff518
2020-09-13 15:54:53.067892+0800 KCObjc[61247:1111510] 1
2020-09-13 15:54:53.067976+0800 KCObjc[61247:1111510] 2
2020-09-13 15:54:53.068055+0800 KCObjc[61247:1111510] 3
2020-09-13 15:54:53.068130+0800 KCObjc[61247:1111510] 4
數(shù)組a的第一個元素的地址就是數(shù)組的地址,通過對數(shù)組的地址進(jìn)行便宜依次得到了數(shù)組后續(xù)的值。
在知道什么是地址偏移后我們再通過地址偏移來分析objc_class的結(jié)構(gòu)。前面我們知道了結(jié)構(gòu)體中的成員包含了:
-
Class ISA繼承自父類,占8字節(jié) -
Class superclass是一個指針,占用8個字節(jié)。 -
cache_t cache以前緩存指針和虛函數(shù)表,占16字節(jié) -
class_data_bits_t bits結(jié)構(gòu)體
那么我們類的信息是否存在于這里bits呢?我們通過查看結(jié)構(gòu)體源碼發(fā)現(xiàn)
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
查看結(jié)構(gòu)體class_rw_t發(fā)現(xiàn):
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};
}
}
這些代碼是否看著很熟悉呢?有methods、properties、protocols,我們可以進(jìn)行驗證這是否可以得到們類的信息。給我們的person添加一個方法- (void)helloClass;
前面我們分析類地址偏移可以得到類里面的所有信息,想查看bits的信息我們需要將地址偏移8+8+18=32字節(jié).
(lldb) x/4gx SYPerson.class
0x1000021f0: 0x00000001000021c8 0x0000000100334140
0x100002200: 0x0000000101134640 0x0002801000000003
//0x1000021f0地址偏移32得到0x100002210
(lldb) p (class_data_bits_t *)0x100002210
(class_data_bits_t *) $1 = 0x0000000100002210
//調(diào)用data()
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001011345e0
//獲取data()方法得到的數(shù)據(jù)class_rw_t類型的$3
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975744
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
//調(diào)用前面查看到方法如methods()
(lldb) p $3.methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002148
arrayAndFlag = 4294975816
}
}
}
//查看list里面數(shù)據(jù)
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100002148
//獲取到$6的內(nèi)容,得到了我們申明的方法helloClass
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "helloClass"
types = 0x0000000100000f9e "v16@0:8"
imp = 0x0000000100000f10 (KCObjc`-[SYPerson helloClass])
}
}
}
//我們添加屬性后同理也可獲得屬性名稱
總結(jié)
我們首先通過分析isa走位分析了實例對象、類、元類的關(guān)系,然后通過查看源碼找到了了類的結(jié)構(gòu)體objc_class,明確了類的底層結(jié)構(gòu)就是結(jié)構(gòu)體,進(jìn)而對結(jié)構(gòu)體里面的成員進(jìn)行了分析,發(fā)現(xiàn)類的屬性方法都存儲在class_data_bits_t bits的結(jié)構(gòu)體中,通過地址偏移也驗證了這個結(jié)論。
