
0.前言
從事 iOS 開(kāi)發(fā)已有 3 年多時(shí)間,大部分時(shí)間都是在用 Objective-C 開(kāi)發(fā) App(最近也在做 OC 與 Swift 的混編實(shí)踐),雖然對(duì) OC 底層知識(shí)有一定的了解,不過(guò)都是零散的片段,計(jì)劃趁著過(guò)年的時(shí)間將這些片段梳理串聯(lián)起來(lái),于是便有了這個(gè)系列。
本文是第 1 篇,從我們平常用的最多的類(lèi)和對(duì)象開(kāi)始,深入探究他們的實(shí)現(xiàn)機(jī)理。
1.概述
我們平常編寫(xiě)的 OC 代碼都會(huì)先編譯成 C/C++代碼,然后再依次翻譯成 匯編代碼 、機(jī)器碼(01代碼),最后,機(jī)器會(huì)自動(dòng)運(yùn)行該機(jī)器語(yǔ)言程序,并將計(jì)算結(jié)果輸出。為了探究 OC 的本質(zhì),通過(guò) C/C++ 是比較合適的方式,因?yàn)橹蟮膮R編和01代碼看著太費(fèi)勁(主要是自己只了解點(diǎn)皮毛(⊙﹏⊙)b),而 OC 本身又不是開(kāi)源的。

2.從一個(gè) ?? 開(kāi)始
2.1 最簡(jiǎn)單的例子
我們先來(lái)看一個(gè)例子:
// 以下代碼位于 main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 創(chuàng)建一個(gè) NSObject 的實(shí)例對(duì)象
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}
如上所示,在 main() 函數(shù)里邊創(chuàng)建了一個(gè) NSObject 的實(shí)例對(duì)象,然后終端執(zhí)行下邊的指令,將代碼編譯成 C/C++ 代碼 (新代碼在 main.cpp 文件中):
clang -rewrite-objc main.m -o main.cpp // -o main.cpp 可以忽略
在 main.cpp 中我們發(fā)現(xiàn)了下邊的結(jié)構(gòu)體,從名字推斷,應(yīng)該是 NSObject 的底層實(shí)現(xiàn):
struct NSObject_IMPL { // NSObject_IMPL <=> NSObject implementation
Class isa;
};
而我們直接查看 NSobject 的聲明:
@interface NSObject <NSObject> { // 移除了用于消除警告的代碼
Class isa OBJC_ISA_AVAILABILITY;
}
與 NSObject_IMPL 對(duì)比后,進(jìn)一步印證了 NSObject_IMPL 是 NSObject 的底層結(jié)構(gòu)的推斷。這里有一個(gè) Class 類(lèi)型的 isa,下面是 Class 的定義:
/// An opaque type that represents an Objective-C class. 表示 OC 中的 class。
typedef struct objc_class *Class;
也就是說(shuō),isa 實(shí)際是一個(gè)指向 struct objc_class 的指針,而且 objc_class 就是 Class 的底層結(jié)構(gòu)。
2.2 稍微復(fù)雜點(diǎn)的例子
現(xiàn)在來(lái)看一種更加復(fù)雜的情況:依次創(chuàng)建 HHStaff 和 HHManager 這 2 個(gè)類(lèi),其中,后者繼承自前者,然后在 main() 函數(shù)中創(chuàng)建一個(gè) HHManager 的實(shí)例。
HHStaff
@interface HHStaff : NSObject {
NSString *name;
}
- (void)doInstanceStaffWork; // 對(duì)象方法
+ (void)doClassStaffWork; // 類(lèi)方法
@end
HHManager
@interface HHManager : HHStaff {
NSInteger officeNum;
}
- (void)doInstanceManagerWork; // 對(duì)象方法
+ (void)doClassManagerWork; // 類(lèi)方法
main.m 文件
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 創(chuàng)建實(shí)例對(duì)象
HHManager *mgr = [[HHManager alloc] init];
}
return 0;
}
終端執(zhí)行 clang -rewrite-objc main.m 將其轉(zhuǎn)成 C/C++ 代碼,整理相關(guān)代碼后,我們可以得出下圖的關(guān)系:

其中,HHManager_IMPL 是 HHManager 的底層結(jié)構(gòu),而 HHStaff_IMPL 是其父類(lèi) HHStaff 的底層結(jié)構(gòu),即子類(lèi)中包含一個(gè)父類(lèi)類(lèi)型的變量,而父類(lèi)結(jié)構(gòu)中又包含一個(gè)父類(lèi)的父類(lèi)(此處是基類(lèi))類(lèi)型變量,而基類(lèi)中包含一個(gè)名為 isa 的指針變量,據(jù)此,可以認(rèn)為子類(lèi) HHManger 經(jīng)編譯后的結(jié)構(gòu)是這樣的:
struct HHManager_IMPL {
Class isa;
NSString *name;
NSInteger officeNum;
};
我們發(fā)現(xiàn),這里包含了一個(gè) isa 指針,而 isa 來(lái)自 NSObject,因?yàn)榇蟛糠诸?lèi)都是直接或間接繼承自 NSObject 的,所以可以認(rèn)為每一個(gè)對(duì)象都包含了一個(gè) isa 指針,至于這個(gè) isa 指針到底是干什么用的,下一小節(jié)就會(huì)講到。
3.OC 的 3 種對(duì)象間的關(guān)系
3.1 OC 中的 3 種對(duì)象
為了搞清楚 isa 指針的作用,有必要先了解一下 OC 的對(duì)象,總共有以下 3 種:
- 實(shí)例對(duì)象(instance),通過(guò)
+alloc方法創(chuàng)建出來(lái)的,如下邊的staffA、staffB:
HHStaff *staffA = [[HHStaff alloc] init];
HHStaff *staffB = [[HHStaff alloc] init];
NSLog(@"實(shí)例對(duì)象:%p - %p", staffA, staffB);
實(shí)例對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:isa 指針 和 其他成員變量。
- 類(lèi)對(duì)象(class),如下邊的
staffClassA、staffClassB:
Class staffClassA = [staffA class]; // <==> Class staffClassA = [[staffA class] class];
Class staffClassB = object_getClass(staffB);
Class staffClassC = [HHStaff class]; // <==> Class staffClassC = [[HHStaff class] class];
NSLog(@"類(lèi)對(duì)象: %p - %p - %p", staffClassA, staffClassB, staffClassC);
類(lèi)對(duì)象中包含的信息如下圖所示,其中,成員變量信息指的是成員變量的描述信息,而非成員變量的值(在實(shí)例對(duì)象里邊)。

- 元類(lèi)對(duì)象(meta-class),如下邊的
staffMetaClassA、staffMetaClassB:
Class staffMetaClassA = object_getClass(staffClassA);
Class staffMetaClassB = object_getClass(staffClassB);
NSLog(@"元類(lèi)對(duì)象:%p - %p", staffMetaClassA, staffMetaClassB);
元類(lèi)對(duì)象的存儲(chǔ)結(jié)構(gòu)與類(lèi)對(duì)象相似,只不過(guò)只有 isa、superclass 和 類(lèi)方法有值,其它均為空。
運(yùn)行上邊的程序后,控制臺(tái)的輸出如下:
2019-01-28 17:36:33.990939+0800 TTTTT[10186:1017842] 實(shí)例對(duì)象:0x100605920 - 0x100606060
2019-01-28 17:36:33.991128+0800 TTTTT[10186:1017842] 類(lèi)對(duì)象: 0x100001260 - 0x100001260 - 0x100001260
2019-01-28 17:36:33.991180+0800 TTTTT[10186:1017842] 元類(lèi)對(duì)象:0x100001238 - 0x100001238
Program ended with exit code: 0
從上述打印結(jié)果可以看出,一個(gè)類(lèi)的實(shí)例對(duì)象可以有多個(gè),但是類(lèi)對(duì)象和元類(lèi)對(duì)象各自只有一個(gè)。
3.2 isa 和 superclass
通過(guò)上一小節(jié),我們知道類(lèi)里邊的信息并不是存在一個(gè)地方,而是分開(kāi)存放在實(shí)例對(duì)象、類(lèi)對(duì)象和元類(lèi)對(duì)象里邊。而將這些對(duì)象聯(lián)系起來(lái)的紐帶就是本小節(jié)要重點(diǎn)討論的 isa 和 superclass 指針。
isa

isa 是用來(lái)聯(lián)系同一個(gè)類(lèi)的實(shí)例對(duì)象、類(lèi)對(duì)象和元類(lèi)對(duì)象的(isa 類(lèi)型是 isa_t,后文會(huì)講到),如上圖所示,通過(guò)實(shí)例對(duì)象里邊的 isa 指針可以找到類(lèi)對(duì)象,根據(jù)類(lèi)對(duì)象里邊的 isa 指針可以找到元類(lèi)對(duì)象。
注意,這里并沒(méi)有說(shuō) isa 指向哪里,而是說(shuō)通過(guò) isa 可以找到哪里,這是因?yàn)閺?64bit 架構(gòu)開(kāi)始,isa 里邊存儲(chǔ)的不再是類(lèi)對(duì)象或者元類(lèi)對(duì)象的地址,而是需要進(jìn)行一次位運(yùn)算 isa.bits & ISA_MASK(依據(jù)見(jiàn)后文 isa_t 的介紹)才能得到相應(yīng)的地址,其中 ISA_MASK 的定義如下:
# if __arm64__ // 64位 真機(jī)
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__ // 64位 模擬器
# define ISA_MASK 0x00007ffffffffff8ULL
# else
# error unknown architecture for packed isa
# endif
注意到 ISA_MASK 中有些位是 0,而和 0 與的話,結(jié)果會(huì)被置為 0,所以可以推測(cè),64bit 架構(gòu)下,isa 里邊可能還存儲(chǔ)了其它信息。
superclass
superclass 是用來(lái)在繼承體系中搜尋父類(lèi)的,如下圖所示:

- 對(duì)于類(lèi)對(duì)象:子類(lèi)(HHManager)的類(lèi)對(duì)象的 superclass 指向父類(lèi)(HHStaff)的類(lèi)對(duì)象,父類(lèi)的類(lèi)對(duì)象的 superclass 指向它的父類(lèi)的類(lèi)對(duì)象;
- 對(duì)于元類(lèi)對(duì)象:子類(lèi)(HHManager)的元類(lèi)對(duì)象的 superclass 指向父類(lèi)(HHStaff)的元類(lèi)對(duì)象,父類(lèi)的元類(lèi)對(duì)象的 superclass 指向它的父類(lèi)的元類(lèi)對(duì)象;
3.3 應(yīng)用
下面我們來(lái)看看在消息發(fā)送過(guò)程中,這 3 種對(duì)象之間是如何親密協(xié)作的。
先貼一張經(jīng)典的關(guān)系圖,實(shí)際就是將上一節(jié)中的 isa 和 superclass 指針?lè)诺搅艘黄穑?/p>

現(xiàn)在以 2.2 節(jié)中的例子為基礎(chǔ),執(zhí)行下邊的操作,即子類(lèi)執(zhí)行父類(lèi)的對(duì)象方法。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 創(chuàng)建實(shí)例對(duì)象
HHManager *mgr = [[HHManager alloc] init];
// 執(zhí)行父類(lèi)的方法
[mgr doInstanceStaffWork];
// => objc_msgSend(mgr, @selector(doInstanceStaffWork));
}
return 0;
}
由于對(duì)象方法存放在類(lèi)對(duì)象里邊,所以首先根據(jù) mgr 的 isa 指針找到它的類(lèi)對(duì)象,然后在類(lèi)對(duì)象的方法列表里邊查找這個(gè)方法,發(fā)現(xiàn)找不到,接著再根據(jù)類(lèi)對(duì)象的 superclass 指針找到父類(lèi)的類(lèi)對(duì)象,然后在父類(lèi)的類(lèi)對(duì)象里邊查找該方法,如果還找不到,就根據(jù)父類(lèi)的 superclass 指針沿著繼承體系繼續(xù)往上找,直到根類(lèi),如果還是找不到,就會(huì)執(zhí)行消息轉(zhuǎn)發(fā)的流程(詳見(jiàn) Objective-C 的消息轉(zhuǎn)發(fā)機(jī)制)。不過(guò),本例中父類(lèi)的類(lèi)對(duì)象里有這個(gè)方法,就不用再往上找了O(∩_∩)O。
如果是類(lèi)方法,則通過(guò)類(lèi)對(duì)象的 isa 指針找到元類(lèi)對(duì)象,然后就依照類(lèi)似查找對(duì)象方法的方式查找類(lèi)方法,只不過(guò)這次是在元類(lèi)對(duì)象的繼承體系里邊查找。
其實(shí),上邊的邏輯省略了一個(gè)非常重要的緩存問(wèn)題,即在每一級(jí)查找時(shí),都會(huì)先查找緩存,然后才去查找方法列表。找到之后,也會(huì)在緩存里邊存一份(即使是在父類(lèi)的類(lèi)對(duì)象或元類(lèi)對(duì)象里邊找到的,也要始終緩存在當(dāng)前類(lèi)對(duì)象或元類(lèi)對(duì)象里),以便提高查找效率。
特例
注意觀察上邊那張關(guān)系圖的右上角,就會(huì)發(fā)現(xiàn),基類(lèi)的元類(lèi)對(duì)象的 superclass 指針指向了自己的類(lèi)對(duì)象,真實(shí)情況是這樣的嗎?我們來(lái)做一個(gè)實(shí)驗(yàn):給 NSObject 添加一個(gè)對(duì)象方法,代碼如下:
@interface NSObject (Extern)
- (void)doInstanceWork;
@end
@implementation NSObject (Extern)
- (void)doInstanceWork {
NSLog(@"這是 NSObject 的對(duì)象方法");
}
@end
然后,在 main.m 中這樣調(diào)用:
int main(int argc, const char * argv[]) {
@autoreleasepool {
[HHStaff doInstanceWork];
}
return 0;
}
即調(diào)用 HHStaff 的類(lèi)方法 +doInstanceWork,不過(guò) HHStaff 里邊并沒(méi)有這個(gè)類(lèi)方法,但是運(yùn)行時(shí)并沒(méi)有報(bào)錯(cuò),控制臺(tái)輸出如下:
2019-02-03 16:09:38.454099+0800 HHH[2667:925051] 這是 NSObject 的對(duì)象方法
也就是說(shuō),確實(shí)如關(guān)系圖所示,執(zhí)行了基類(lèi)的類(lèi)對(duì)象里邊存儲(chǔ)的對(duì)象方法??梢赃@么來(lái)理解,OC 的方法調(diào)用經(jīng)編譯后都會(huì)轉(zhuǎn)成這樣的函數(shù)調(diào)用:objc_msgSend(object, @selector(methodName)) ,這里并沒(méi)有指明是類(lèi)方法還是對(duì)象方法,也就是不關(guān)心是對(duì)象方法還是類(lèi)方法,如果 object 是實(shí)例對(duì)象,就會(huì)去類(lèi)對(duì)象里查找方法,如果 object 是類(lèi)對(duì)象,就會(huì)去元類(lèi)對(duì)象里邊查找。
4.Class 的結(jié)構(gòu)
前邊我們說(shuō)過(guò),類(lèi)中的方法、屬性、協(xié)議等重要信息都存儲(chǔ)在類(lèi)對(duì)象和元類(lèi)對(duì)象里邊,這兩者的結(jié)構(gòu)相同,都是 Class 類(lèi)型的,而 Class 的結(jié)構(gòu)實(shí)際就是 struct objc_class,因此我們的目的就是要弄清楚 struct objc_class 的結(jié)構(gòu)。
在 objc 源碼的 objc-runtime-new.h 中找到了 objc_class 的最新定義:
struct objc_class : objc_object {
// Class ISA; // isa 不再放這里
Class superclass;
cache_t cache; // 1.緩存
class_data_bits_t bits;
class_rw_t *data() { // 2.class_rw_t
return bits.data();
}
// *** 此處略去好多行 O(∩_∩)O~
}
既然 C++ 的結(jié)構(gòu)體是可以繼承的,那么我們來(lái)看看它繼承的結(jié)構(gòu)體 objc_object 里邊都有什么:
struct objc_object {
private:
isa_t isa; // 3.isa,注意是私有
public:
// 此方法返回的不是 tagged pointer 對(duì)象
Class ISA();
// 此方法返回的可能是一個(gè) tagged pointer 對(duì)象
Class getIsa();
// *** 此處又略去好多行 O(∩_∩)O~
}
以上就是 objc_class 的表層結(jié)構(gòu),下面針對(duì)其中的 3 各主要部分做一個(gè)相對(duì)深入點(diǎn)的討論。
4.1 cache_t
cache_t 就是前文提到的方法緩存,其結(jié)構(gòu)如下所示(做了適當(dāng)精簡(jiǎn)):
struct cache_t {
struct bucket_t *_buckets; // 散列表
mask_t _mask; // 散列表的長(zhǎng)度 - 1
mask_t _occupied; // 已經(jīng)緩存的方法數(shù)量
public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
// *** 此處又略去好多行 O(∩_∩)O~
// 擴(kuò)展空間
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
// 查詢(xún)緩存
struct bucket_t * find(cache_key_t key, id receiver);
// *** 此處略去好多行 O(∩_∩)O~
};
cache_t 里邊有一個(gè)散列表(哈希表)_buckets,里邊是一個(gè)個(gè)的 struct bucket_t,用于緩存方法。bucket_t 的結(jié)構(gòu)如下所示:
struct bucket_t {
private:
cache_key_t _key; // 用 SEL 做 key
IMP _imp; // 函數(shù)的內(nèi)存地址 做 value
public:
inline cache_key_t key() const { return _key; }
inline IMP imp() const { return (IMP)_imp; }
inline void setKey(cache_key_t newKey) { _key = newKey; }
inline void setImp(IMP newImp) { _imp = newImp; }
void set(cache_key_t newKey, IMP newImp);
};
現(xiàn)在,我們看一下如何查詢(xún)緩存,即 find() 函數(shù)的實(shí)現(xiàn):
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m); // 根據(jù) k 與 m 算出一個(gè)下標(biāo):begin = k & m
mask_t i = begin;
do { // 根據(jù)下標(biāo)取值,并驗(yàn)證做了一個(gè)異常處理,即不同 key 得到相同下標(biāo)的問(wèn)題
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
查詢(xún)的基本邏輯是:
先根據(jù)傳入的 k(即key) 和 m(即mask) 算出一個(gè)下標(biāo)
begin = k & m-
然后用這個(gè)下標(biāo) begin 去散列表里取值,用取到的值 (bucket) 里邊的 key 與 傳入的 k 作比較,
如果相等,就將取到的值 (bucket) 返回;
-
如果不等,利用
cache_next()函數(shù) (如下) 算出一個(gè)新的下標(biāo),再去取值比較;#if __arm__ || __x86_64__ || __i386__ // 各種模擬器 static inline mask_t cache_next(mask_t i, mask_t mask) { return (i+1) & mask; } #elif __arm64__ // 64bits 真機(jī) static inline mask_t cache_next(mask_t i, mask_t mask) { // 如果 i 不為 0,則返回 i-1;否則返回 mask return i ? i-1 : mask; } #else #error unknown architecture #endif 如此循環(huán),最后如果新算出來(lái)的下標(biāo)等于 begin,則退出循環(huán),說(shuō)明緩存里沒(méi)有對(duì)應(yīng)的方法。
4.2 class_rw_t
class_rw_t 是通過(guò) bit 的 data() 函數(shù)獲取的,從名稱(chēng)可以看出來(lái),它是可讀可寫(xiě)的(rw),其基本結(jié)構(gòu)及說(shuō)明如下:
struct class_rw_t {
// *** 此處又略去好多行 O(∩_∩)O~
const class_ro_t *ro;
method_array_t methods; // 方法列表
property_array_t properties; // 屬性列表
protocol_array_t protocols; // 協(xié)議列表
// *** 此處又略去好多行 O(∩_∩)O~
}
4.2.1 class_ro_t
上邊的 class_rw_t 里有一個(gè) readonly 的 class_ro_t *ro,class_ro_t 的結(jié)構(gòu)及各元素的說(shuō)明如下:
struct class_ro_t {
// *** 此處略去好多行 O(∩_∩)O~
const char * name; // 類(lèi)名
method_list_t * baseMethodList; // 方法列表
protocol_list_t * baseProtocols; // 協(xié)議列表
const ivar_list_t * ivars; // 成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties; // 屬性列表
method_list_t *baseMethods() const {
return baseMethodList;
}
};
class_ro_t 里邊存放的是編譯完成時(shí)類(lèi)結(jié)構(gòu)里邊的方法、屬性、協(xié)議及成員變量等信息。class_rw_t 里邊是在運(yùn)行時(shí)擴(kuò)展了累的方法、屬性等信息以后的結(jié)構(gòu),比如,分類(lèi)中的方法就是加到了這個(gè)結(jié)構(gòu)里。
class_ro_t 里邊有成員變量,而且是只讀的,但 class_rw_t 里沒(méi)有,這也解釋了為什么不能通過(guò)分類(lèi)添加成員變量。當(dāng)然分類(lèi)里是可以添加屬性的,只不過(guò)這樣添加的屬性只能生成 setter 和 getter 的聲明,還需要自己設(shè)法完成他們的實(shí)現(xiàn),至于如何實(shí)現(xiàn),下一篇 runtimen 會(huì)講到。
4.2.2 method_t
后面的幾篇還會(huì)用到 '方法' 相關(guān)的底層概念 method_t,這里就簡(jiǎn)單說(shuō)明一下。上文的源碼中與 method_t 關(guān)系最近的應(yīng)該要數(shù) method_list_t 了,現(xiàn)在我們就通過(guò)它探究一下 method_t,下邊是其源碼:
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
bool isFixedUp() const;
void setFixedUp();
// 函數(shù):用于獲取具體 method_t 的下標(biāo) index
uint32_t indexOfMethod(const method_t *meth) const {
uint32_t i = (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
assert(i < count);
return i;
}
};
從源碼能夠看出來(lái), method_list_t 繼承自 entsize_list_tt,后者的定義如下,不過(guò)這里略去了定義,重點(diǎn)看上方的說(shuō)明部分:
/***********************************************************************
* entsize_list_tt<Element, List, FlagMask>
* Generic implementation of an array of non-fragile structs. // 一種xxx結(jié)構(gòu)體數(shù)組的實(shí)現(xiàn)
*
* Element is the struct type (e.g. method_t)
* List is the specialization of entsize_list_tt (e.g. method_list_t)
* FlagMask is used to stash extra bits in the entsize field
* (e.g. method list fixup markers)
**********************************************************************/
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
//...
}
從注釋我們了解到,模板中的 List 是由一個(gè)個(gè)的 Element 組成的數(shù)組,結(jié)合前邊 method_list_t 的定義可知,method_list_t 是由 method_t 組成的數(shù)組,method_t 的結(jié)構(gòu)如下:
struct method_t {
SEL name; // 名字
const char *types; // 類(lèi)型
MethodListIMP imp; // 指針
//...
};
method_t 的結(jié)構(gòu)中主要包含3個(gè)元素:①方法名 name、②方法類(lèi)型 char *types、③指向方法實(shí)現(xiàn)的指針 imp。
注:
property_t和protocol_t與此類(lèi)似,就不多做介紹了,詳見(jiàn) 代碼注釋。
4.3 isa_t
4.3.1 isa_t
objc_object 這個(gè)結(jié)構(gòu)體里邊 isa 的類(lèi)型是個(gè)共用體 union isa_t ,其結(jié)構(gòu)如下(其中 struct {...} 的作用只是讓各位的含義可視化):
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD) // 位域
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
從 64 位架構(gòu)開(kāi)始引入了位域,可以在isa 中存儲(chǔ)更多信息,上邊結(jié)構(gòu)體中的 ISA_BITFIELD 定義如下:
// isa.h
# if __arm64__ // 64位真機(jī)
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# elif __x86_64__ // 64位模擬器·
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# else
# error unknown architecture for packed isa
# endif
下面這張圖以 64 位真機(jī)為例,詳細(xì)說(shuō)明了各位的作用:

前邊 3.2 說(shuō)過(guò),從 64 位架構(gòu)開(kāi)始,需要通過(guò) isa.bits & ISA_MASK 才能得到對(duì)應(yīng)類(lèi)對(duì)象或元類(lèi)對(duì)象的地址,其實(shí)就是為了取出 shiftcls 部分。
在前文 struct objc_object 的結(jié)構(gòu)中,我們發(fā)現(xiàn) isa 是私有的,外部只能通過(guò) ISA() 和 getIsa() 這兩個(gè)方法訪問(wèn),下面分別看一下 ISA() 的源碼。
#if SUPPORT_NONPOINTER_ISA
inline Class objc_object::ISA()
{
// 如果是 TaggedPointer 就會(huì)中斷言
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
上邊是 ISA() 的源碼,其中條件編譯的條件是 SUPPORT_INDEXED_ISA,定義如下:
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa field as an index into a class table.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
其中,_ _ ARM_ARCH_7K _ _ 是 Apple Watch 會(huì)用到的,LP64 指的是 Long Pointer 64位,現(xiàn)在絕大多數(shù) Unix 平臺(tái)均使用 LP64 數(shù)據(jù)模型,所以一般情況下 SUPPORT_INDEXED_ISA 的值為 0,也就是說(shuō) ISA() 會(huì)執(zhí)行 else 中的代碼,即 isa.bits & ISA_MASK。
4.3.2 Tagged Pointer
我們注意到 getIsa() 的如下說(shuō)明,也就是說(shuō),getIsa() 允許當(dāng)前對(duì)象(this)是一個(gè) Tagged Pointer 對(duì)象。那么,下面我們就來(lái)了解一下這個(gè)東東。
getIsa() allows this to be a tagged pointer object.
Tagged Pointer 是蘋(píng)果在發(fā)布 iPhone 5s(搭載 64 位架構(gòu)的 A7處理器)時(shí)提出的,它的優(yōu)勢(shì)在于,對(duì)于小對(duì)象(如NSNumber、NSDate等)能夠大大地節(jié)省內(nèi)存和提高執(zhí)行效率。
我們以下面這行代碼為例,比較一下引入 Tagged Pointer 前后占用內(nèi)存的變化。
NSNumber *num = @(2);
如下圖所示,當(dāng)從 32 位機(jī)器遷移到 64 位機(jī)器后,如果沒(méi)有引入 Tagged Pointer,雖然邏輯未改變,但是所占用的內(nèi)存會(huì)翻倍;如果引入了 Tagged Pointer,因?yàn)榇鎯?chǔ) NSNumber 變量(此處為 @(2))本身的值常常用不了 8 個(gè)字節(jié),于是會(huì)將一個(gè)對(duì)象的指針(8 個(gè)字節(jié))拆成兩部分,一部分直接保存數(shù)據(jù),另一部分作為特殊標(biāo)記,標(biāo)記是否是 “特殊指針”。

當(dāng)然,這也是有一定限制的,當(dāng) 8 個(gè)字節(jié)可以承載要表示的數(shù)值時(shí),系統(tǒng)就會(huì)以 Tagged Pointer 的方式生成指針;如果 8 個(gè)字節(jié)承載不了,則又會(huì)用以前的方式來(lái)生成普通的指針。
5.小結(jié)
關(guān)于 Class 的討論就先討論到這里,可能有些地方理解的還不是很到位,后邊會(huì)及時(shí)更新的 O(∩_∩)O~