在分析類的結(jié)構(gòu)之前,我們需要先搞清楚類的內(nèi)存分布情況。
類的內(nèi)存分布
首先創(chuàng)建一個(gè)Person類:
// Person 類
@interface Person : NSObject
- (void)sayHello;
+ (void)sayHi;
@end
@implementation Person
- (void)sayHello {
NSLog(@"Say Hello");
}
+ (void)sayHi {
NSLog(@"Say Hi");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
NSLog(@"%@",person);
}
return 0;
}

通過上圖中可以看出 0x00000001000012e8 和 0x00000001000012c0 為什么打印出的結(jié)果都是 Person 呢?這個(gè)東西其實(shí)就是元類。
那么什么是元類呢?
元類
每一個(gè)對象都對應(yīng)一個(gè)類。
Person類就是person對象的類,換句話說就是person對象的isa指針指向Person對應(yīng)的結(jié)構(gòu)體;[Person class]也是對象,描述它的類就是元類,換句話說[Person class]對象的isa指向的就是元類元類的定義和創(chuàng)建都是由編譯器自動(dòng)完成。
元類保存了類方法的列表。當(dāng)一個(gè)類方法被調(diào)用時(shí),元類會(huì)首先查找它本身是否有該類的方法的實(shí)現(xiàn),如果沒有則該類會(huì)向它的父類查找該方法,知道一直找到繼承鏈的頭。


從上圖中我們可以看到,我們通過isa可以一直找到NSObject類。通過 person對象的isa指針可以找到Person類,通過Person的isa指針可以找到元類Person,而通過元類Person的isa指針又可以找元類Person的元類NSObject,通過元類NSObject的isa指針找到的還是NSObject,其實(shí)這個(gè)時(shí)候的NSObject叫做根元類
但是NSObject的類和 NSObject.class 的指針地址不一樣呢?是NSObject類會(huì)在內(nèi)存中存在多份嗎?
我們通過如下代碼打印來看下:
void getClassInfo() {
Class class1 = [Person class];
Class class2 = [Person alloc].class;
Class class3 = object_getClass([Person alloc]);
NSLog(@"\n%p\n%p\n%p\n",class1,class2,class3);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
getClassInfo();
}
return 0;
}
輸出結(jié)果如下:
2020-09-15 20:49:21.406345+0800 ObjectAnalysis[34085:811450]
0x1000012f0
0x1000012f0
0x1000012f0
Program ended with exit code: 0
可以看出其實(shí)類對象在整個(gè)內(nèi)存中只有一份。
因此我們可以得出一個(gè)著名的isa的走位圖。
isa的走位圖&繼承圖

isa查找流程
-
實(shí)例對象的isa指針類對象 -
類對象的isa指向元類對象 -
元類對象的isa指向根元類 -
根元類的isa指向它自己本身,從而形成了閉環(huán)
繼承關(guān)系
類對象的繼承關(guān)系
-
類繼承于它的父類 -
父類繼承它的父類
... - 直到找到
根類,也就是NSObject -
NSObject則繼承于nil,這也就是所有的根源,即無中生有
元類的繼承關(guān)系
-
子類的元類繼承與父類的元類 -
父類的元類繼承它的父類的元類
... - 直到找到
根元類 - 而
根元類則是繼承于NSObject
tips:實(shí)例對象是沒有繼承關(guān)系的,只有類才有繼承關(guān)系
對象的本質(zhì)
對象的本質(zhì)其實(shí)就是結(jié)構(gòu)體,而編譯到底層都是繼承自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();
}
// 代碼過多,自動(dòng)省略
...
};
// objc_object
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
// 代碼過多,自動(dòng)省略
...
};
通過源碼可以看出 objc_class 其實(shí)就是繼承自 objc_object。
所以我們可以看出所有的 對象、類和元類都有isa指針,是因?yàn)槎祭^承自 objc_object,而 objc_object中有個(gè)私有變量isa。
objc_object、objc_class、isa、object、NSObject的關(guān)系如下圖所示:

我們接下來重點(diǎn)就是分析類的結(jié)構(gòu)了
類的結(jié)構(gòu)分析
再開始前,先分析下objc_class的結(jié)構(gòu)圖

- isa 是指向元類的指針
- superclass 是指向當(dāng)前類的父類
- cache 是用于緩存方法的,用于加速方法的調(diào)用
- bits 是存儲(chǔ)類的方法、屬性、協(xié)議等信息的地方
一般我們獲取到bits,然后才能獲取到我們需要的信息,那么我們就需要進(jìn)行32位指針的偏移,為什么是32位指針的偏移,請看下面的代碼段分析:
struct objc_class : objc_object {
// Class ISA; // 指針占 8 字節(jié)
Class superclass; // 指針占 8 字節(jié)
cache_t cache; // 占 16 字節(jié),具體分析,看cache_t 結(jié)構(gòu)體的分析
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
// 代碼過多,自動(dòng)省略
...
};
// mask_t 的定義
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
// 結(jié)構(gòu)體指針字節(jié)大小,看里面的成員變量,而大部分都是 static 和方法都不存在結(jié)構(gòu)體里面
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 結(jié)構(gòu)體指針 8 字節(jié)
explicit_atomic<mask_t> _mask; // uint32_t 占 4 字節(jié)
#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;
#if __LP64__
uint16_t _flags; // 占 2 字節(jié)
#endif
uint16_t _occupied; // 占 2 字節(jié)
// 方法代碼過多,自動(dòng)省略
...
};
class_data_bits_t
在objc_class結(jié)構(gòu)體中的注釋中已經(jīng)寫到class_data_bits_t相當(dāng)于class_rw_t指針加上rr/alloc的標(biāo)志。
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
而在class_data_bits_t結(jié)構(gòu)體中為我們提供了一個(gè)用于返回其中的class_rw_t *的方法
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
// 代碼過多,自動(dòng)省略
...
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// 代碼過多,自動(dòng)省略
...
};
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<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
// 省略過多的代碼
...
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結(jié)構(gòu)體中提供的methods()、properties()、protocols()獲取到對應(yīng)的方法、屬性和協(xié)議。
那么接下來我們通過lldb調(diào)試來查找對應(yīng)的class_data_bist_t bits,查看相應(yīng)的信息。

當(dāng)我們獲取到class_rw_t結(jié)構(gòu)體信息后,接下來想要獲取到方法、屬性或者協(xié)議,就需要通過調(diào)用其提供的方法來進(jìn)行獲取。
屬性列表 properties()

- 1、通過
properties()可以獲取到對應(yīng)的屬性列表 - 2、通過get方法訪問第一個(gè)屬性,可以發(fā)現(xiàn)是我們定義的
name屬性
【error】但是嘗試去獲取第二個(gè)屬性的時(shí)候,發(fā)現(xiàn)數(shù)組越界了,這是為什么呢?我們不是還定義了一個(gè)hobby嗎?
由此引出成員變量、實(shí)例變量和屬性的區(qū)別
成員變量/實(shí)例變量/屬性
成員變量: Member Variable declarations
類: Class (description/template for an object)
實(shí)例: Instance (manifestation of a class)
消息: Message (sent to object to make it act)
方法: Method (code invoked by a Message)
實(shí)例變量: Instance Variable (object-specific storage)
超類/子類: Superclass/Subclass (Inheritance)
協(xié)議: Protocol (non-class-specific methods)
從上面的解釋,可以看出,實(shí)例是針對類而言的,。實(shí)例是指類的聲明,由此推理,實(shí)例變量是指由類聲明的對象。
而屬性和成員變量的區(qū)別在于有無setter、getter方法。
方法列表 methods()
我們還是先通過lldb調(diào)試來查找方法列表。

通過上圖的lldb調(diào)試過程,我們可以發(fā)現(xiàn):
-
method_list_t中有方法的總個(gè)數(shù)count=4 - 會(huì)優(yōu)先存儲(chǔ)我們定義的實(shí)例方法
- 會(huì)有一個(gè)隱藏的析構(gòu)函數(shù)
.cxx_destruct,實(shí)現(xiàn)ARC下自動(dòng)釋放內(nèi)存的工作 -
setter、getter方法的存儲(chǔ)
其實(shí)探索到這里,發(fā)現(xiàn)我們還遺留了兩個(gè)問題:
1、成員變量的存儲(chǔ)
2、類方法的存儲(chǔ)
成員變量的存儲(chǔ)
其實(shí)通過objc_class的源碼,我們可以知道里面其實(shí)還有個(gè)ro方法,會(huì)返回一個(gè)class_ro_t的結(jié)構(gòu)體的信息。打印信息如下:

在class_ro_t結(jié)構(gòu)體中其實(shí)有很多的成員變量和方法,其實(shí)class_rw_t中的ro,在類的編譯期就已經(jīng)確定了屬性、方法以及要遵循的協(xié)議,objc_class中的bits中的data部分存放了ro信息。而在runtime運(yùn)行之后,準(zhǔn)確的說是在運(yùn)行runtime的realizeClass方法時(shí),會(huì)生成class_rw_t結(jié)構(gòu)體,該結(jié)構(gòu)體包含了class_ro_t,并且更新了data部分。換成了class_rw_t結(jié)構(gòu)體的地址。
由上圖的llbd打印出的ivars信息中,可以看到,我們生命的屬性,也會(huì)在ivars中生成一個(gè) _name的成員變量。這也就是我們?yōu)槭裁纯梢酝ㄟ^ _name和self.name來訪問屬性的原因了。
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;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
method_list_t *baseMethods() const {
return baseMethodList;
}
class_ro_t *duplicate() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
return ro;
} else {
size_t size = sizeof(*this);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
return ro;
}
}
};
類方法的存儲(chǔ)
其實(shí)我們從上面的isa走位圖中就可以知道一點(diǎn),所有的實(shí)例對象的isa都是指向其元類的,那么我們是否可以查看其元類中的方法列表。下面我們通過lldb調(diào)試來看一下。

所以可以看出對象中的類方法都是存儲(chǔ)在其元類中的。