
Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言,這就意味著消息傳遞和類(lèi)以及對(duì)象的創(chuàng)建都在運(yùn)行時(shí)完成,這個(gè)核心的庫(kù)是由 C\C++ 和匯編編寫(xiě)的,保證其系統(tǒng)運(yùn)行的高效性。
isa
這個(gè)老朋友我們見(jiàn)了無(wú)數(shù)次了,在 arm64 架構(gòu)之前,isa 僅僅是一個(gè)普通的指針,存儲(chǔ) Class、Meta-Class 對(duì)象的地址。
在 arm64 后,isa 變成了聯(lián)合體(union)類(lèi)型。這個(gè)類(lèi)型可以像 struct 那樣存儲(chǔ)更多的信息。
我們可在 objc 源碼中看到 isa 的結(jié)構(gòu)并非是 Class 類(lèi)型而是聯(lián)合體:
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
};
ISA_BITFIELD定義是這樣的:
# 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 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
這種表現(xiàn)形式是位域。
存儲(chǔ)的某些信息是不需要一個(gè)完整的字節(jié)的,僅僅需要 1 個(gè)或幾個(gè)二進(jìn)制位,就可以通過(guò)位域來(lái)存儲(chǔ)。位域的形式為:類(lèi)型說(shuō)明符(int、unsigned int 或 signed int)位域名: 位域長(zhǎng)度,如:
int a: 8;
位域中的字段
通過(guò)位域來(lái)存儲(chǔ)更豐富的信息,正是蘋(píng)果對(duì)內(nèi)存優(yōu)化的體現(xiàn),上節(jié)中位域列表的各個(gè)字段的含義為:
nonpointer:0 表示普通指針,存儲(chǔ)類(lèi)對(duì)象及元類(lèi)對(duì)象的地址,1 表示優(yōu)化后的指針,通過(guò)位域列表存儲(chǔ)更多信息。
has_assoc:是否設(shè)置過(guò)關(guān)聯(lián)對(duì)象,若沒(méi)有,則 release 時(shí)更快。
has_cxx_dtor:是否有 C++ 的析構(gòu)函數(shù),若沒(méi)有,release 時(shí)更快。
shiftcls:存儲(chǔ)類(lèi)對(duì)象和元類(lèi)對(duì)象的內(nèi)存地址。
magic:用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化。
weakly_referenced:是否被若引用指向。
deallocating:對(duì)象是否正在釋放。
extra_rc:存儲(chǔ)的值為引用計(jì)數(shù)器減 1。
has_sidetable_rc:引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在 isa 中,若為 1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫 SideTable 的類(lèi)的屬性中。
做個(gè)簡(jiǎn)單的驗(yàn)證,假如有 Test 類(lèi),無(wú)屬性,在另一個(gè)類(lèi)中使用它:
Test* t = [[Test alloc] init];
NSLog(@"%@", t);
在第二句加斷點(diǎn),進(jìn)入 LLDB 調(diào)試環(huán)境借助命令:
print/x t->isa
得到打印:
(Class) $0 = 0x000001a10000cdc1 Test
將該地址復(fù)制到系統(tǒng)計(jì)算器中:

最后一位為 1 說(shuō)明 nonpointer 位為 1,說(shuō)明該 isa 指針是 arm64 優(yōu)化過(guò)后的指針,存儲(chǔ)了更多信息。
倒數(shù)第二位為 0,說(shuō)明 has_assoc 位為 0,說(shuō)明該類(lèi)未設(shè)置關(guān)聯(lián)對(duì)象,例子中我沒(méi)有給 Test 類(lèi)設(shè)置關(guān)聯(lián)對(duì)象。
倒數(shù)第三位為 0,說(shuō)明 has_cxx_dtor 位為 0,說(shuō)明該類(lèi)沒(méi)有析構(gòu)函數(shù)。(析構(gòu)函數(shù)類(lèi)似 dealloc 函數(shù))
接下來(lái)的 33 位,如圖:

表示字段 shiftcls,存放著類(lèi)對(duì)象地址或者元類(lèi)對(duì)象的值。
接下來(lái)的 6 位 01 1010 表示字段 magic,表示對(duì)象已經(jīng)初始化成功,執(zhí)行完 alloc 和 init 后它的值為 1a,在源碼中也有體現(xiàn):
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
接下來(lái)的一位為 0,為 weakly_referenced 位,表示該對(duì)象未被弱引用指向過(guò)。
接下來(lái)一位為 0,為 deallocating 位,表示該對(duì)象沒(méi)有正在被釋放。
接下來(lái)一位為 0,為 has_sidetable_rc 位,表示引用計(jì)數(shù)存儲(chǔ)在后 19 位,若引用計(jì)數(shù)并沒(méi)有存在后 19 位的時(shí)候該位為 1.
最后十九位為 0,為 extra_rc 位,用來(lái)存放引用計(jì)數(shù) - 1。所以都是 0。
在 Objective-C 對(duì)象的分類(lèi)以及 isa、superclass 指針 中提到,在 arm64 架構(gòu)下,isa 需要和 ISA_MASK 位運(yùn)算一次才能得到真正的類(lèi)對(duì)象或者元類(lèi)對(duì)象地址,正是因?yàn)?isa 優(yōu)化后存儲(chǔ)了更多的信息,只有中間的 33 位是類(lèi)對(duì)象或者元類(lèi)對(duì)象地址,所以需要對(duì) ISA_MASK 進(jìn)行一次位運(yùn)算。
Class
Objective-C 中類(lèi)對(duì)象和元類(lèi)對(duì)象都能用 Class 表示,或者通俗點(diǎn)說(shuō),元類(lèi)對(duì)象是特殊的類(lèi)對(duì)象。在底層為 objc_class。
在 objc 源碼中可看到 objc_class 的結(jié)構(gòu):
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
objc_object 中有:
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
所以可簡(jiǎn)化為:
struct objc_class : objc_object {
Class isa;
Class superclass;
cache_t cache; // 方法緩存
class_data_bits_t bits; // 用于獲取具體類(lèi)信息
...
}
其中 bits 和 FAST_DATA_MASK 進(jìn)行 & 運(yùn)算可得到 class_rw_t,class_rw_t 的結(jié)構(gòu)為:
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; // 方法列表
property_array_t properties; // 屬性列表
protocol_array_t protocols; // 協(xié)議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
...
}
其中 class_ro_t 是一個(gè)只讀的結(jié)構(gòu)體:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // 實(shí)例對(duì)象占用的內(nèi)存空間
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; // 類(lèi)名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // 成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
...
};
為研究 Class 里面的結(jié)構(gòu),我們可自己實(shí)現(xiàn) Class 的底層機(jī)制,包括 class_ro_t、class_rw_t、緩存列表、協(xié)議列表等等等,篇幅過(guò)長(zhǎng)不貼出代碼。接下來(lái)的例子中將使用這份代碼進(jìn)行轉(zhuǎn)換。
和 objc 源碼不同的是,方法列表、屬性列表、協(xié)議列表這些二維數(shù)組的成員用了一維數(shù)組代替。
class_rw_t
class_rw_t 中里面的方法列表、屬性列表、協(xié)議列表都是二維數(shù)組,并且是可讀可寫(xiě)的,包含了本類(lèi)和分類(lèi)中的內(nèi)容。
方法列表的二維數(shù)組,同理屬性和協(xié)議列表的二維數(shù)組:

這樣可以動(dòng)態(tài)增加方法或者修改方法,并且二維數(shù)組的每個(gè)方法列表都有可能是一個(gè)分類(lèi)的方法列表。
class_ro_t
class_ro_t 中的 baseMethodList、baseProtocols、ivars、baseProperties 是一維數(shù)組的,只讀,包含了類(lèi)的初始內(nèi)容。
也就是說(shuō)本類(lèi)的協(xié)議、屬性、方法等信息在這個(gè)一維數(shù)組里面。

這份不變的 baseMethodList 和 class_rw_t 中最后一個(gè)元素是一樣的,在 runtime 初始化的過(guò)程中,會(huì)根據(jù)類(lèi)的初始信息來(lái)創(chuàng)建 class_rw_t 的成員:
static Class realizeClass(Class cls)
{
...
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
...
}
method_t
method_t 是對(duì)方法/函數(shù)的封裝,也是個(gè)結(jié)構(gòu)體:
struct method_t {
SEL name; // 函數(shù)名
const char *types; // 編碼(返回值類(lèi)型、參數(shù)類(lèi)型)
MethodListIMP imp; // 指向函數(shù)的指針(函數(shù)地址)
};
IMP 代表函數(shù)的具體實(shí)現(xiàn):
using MethodListIMP = IMP;
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
SEL 代表方法/函數(shù)名,一般叫做選擇器,底層和 char* 類(lèi)似,可以通過(guò) @selector() 和 sel_registerName() 獲得。可以通過(guò) sel_getName() 和 NSStringFromSelector() 轉(zhuǎn)成字符串。
那么可得知,不同類(lèi)中相同名字的方法,所對(duì)應(yīng)的方法選擇器是相同的。
我們?cè)?Test 類(lèi)中添加實(shí)例方法 test():
- (void)test {
NSLog(@"%s", __func__); //加斷點(diǎn)
}
然后運(yùn)行:
Test* t = [[Test alloc] init];
v_objc_class* tCls = (__bridge v_objc_class*)[Test class];
class_rw_t* data = tCls->data();
[t test]; // 加斷點(diǎn)
進(jìn)入調(diào)試環(huán)境看到 data 中的 test() 信息:

打印得:
Printing description of data->methods->first.imp:
(IMP) imp = 0x00000001002ce654 (Test_3`-[Test test] at Test.m:13)
來(lái)到第二個(gè)斷點(diǎn) Debug->Debug Workflow->Always Show Disassembly:

發(fā)現(xiàn)畫(huà)圈部分就是這個(gè)函數(shù)的起始地址:0x00000001002ce654
types
types 包含了函數(shù)的返回值、參數(shù)編碼的字符串:
| 返回值 | 參數(shù)1 | 參數(shù)2 | ... | 參數(shù)n |
|---|
在上節(jié)的調(diào)試環(huán)境 data 信息截圖可看到 types 是:
v16@0:8
這樣的形式,其中
| 解釋 | |
|---|---|
| v | 代表返回值是 void |
| 16(第一個(gè)數(shù)字) | 表示所有參數(shù)所占字節(jié)數(shù) |
| @ | 第一個(gè)參數(shù),id 類(lèi)型 |
| 0 | 表示第一個(gè)參數(shù)(id)從 0 開(kāi)始 |
| : | 代表 SEL |
| 8 | 表示 SEL 從 8 開(kāi)始 |
以上就是 objc 通過(guò)字符串來(lái)描述一個(gè)函數(shù)的返回值及參數(shù)信息。
Type Encoding
iOS 中提供了一個(gè)叫 @encode 的指令,可以將具體的類(lèi)型表示成字符串編碼,如打?。?/p>
NSLog(@"%s", @encode(int));
NSLog(@"%s", @encode(NSString));
NSLog(@"%s", @encode(id));
NSLog(@"%s", @encode(void));
結(jié)果:
i
{NSString=#}
@
v
完整的編碼表:
| 編碼 | 釋義 |
|---|---|
| c | A char |
| i | An int |
| s | A short |
| l | A longl is treated as a 32-bit quantity on 64-bit programs |
| q | A long long |
| C | An unsigned char |
| I | An unsigned int |
| S | An unsigned short |
| L | An unsigned long |
| Q | An unsigned long long |
| f | A float |
| d | A double |
| B | A C++ bool or a C99 _Bool |
| v | A void |
| * | A character string (char *) |
| @ | An object (whether statically typed or typed id) |
| # | A class object (Class) |
| : | A method selector (SEL) |
| [array type] | An array |
| {name=type...} | A structure |
| (name=type...) | A union |
| bnum | A bit field of num bits |
| ^type | A pointer to type |
| ? | An unknown type (among other things, this code is used for function pointers) |
方法緩存
在 objc_class 的結(jié)構(gòu)體中,cache_t 類(lèi)型的 cache 成員是用來(lái)緩存方法的,它通過(guò)哈希表來(lái)緩存曾經(jīng)調(diào)用過(guò)的方法,可以提高查找速度。
在 Objective-C 對(duì)象的分類(lèi)以及 isa、superclass 指針一文中,得知實(shí)例方法或者類(lèi)方法都是通過(guò) isa 指針找到類(lèi)對(duì)象或者元類(lèi)對(duì)象的方法列表,遍歷,有則調(diào)用,沒(méi)有則通過(guò) superclass 指針在父類(lèi)中找方法列表,遍歷,有則調(diào)用,沒(méi)有則繼續(xù)向上找... 若一個(gè)函數(shù)調(diào)用很多次,造成的開(kāi)銷(xiāo)是很大的,所以在函數(shù)第一次調(diào)用的時(shí)候,會(huì)緩存到 cache 中,這樣就不用每次都層層尋找而是從哈希表中取出直接調(diào)用。
cache_t 的結(jié)構(gòu)為:
struct cache_t {
struct bucket_t *_buckets; // 哈希表
mask_t _mask; // 哈希表長(zhǎng)度 - 1
mask_t _occupied; // 已經(jīng)緩存的方法數(shù)量
}
bucket_t 是一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)為:
struct bucket_t {
cache_key_t _key; // SEL 作為 key
MethodCacheIMP _imp; // 函數(shù)內(nèi)存地址
}
緩存方法查找原理
這里有個(gè)很高效的算法:目標(biāo)函數(shù)和 _mask 進(jìn)行 & 運(yùn)算可以直接得到目標(biāo)索引,憑借目標(biāo)索引直接在哈希表中取函數(shù)地址進(jìn)行調(diào)用。

該索引在 test() 方法放入哈希表的時(shí)候就已經(jīng)確定。
當(dāng)然存在這種情況,假如哈希表數(shù)組為 0,而 @selector(test) & _mask 結(jié)果為 3,則情況為:

也就是說(shuō),其他位都成了預(yù)留位置且都是 NULL,這樣的做法雖然高效,但卻是以犧牲內(nèi)存空間為代價(jià)的。
而且可以發(fā)現(xiàn),地址 & _mask 的結(jié)果是小于等于 _mask 的。
那么假如兩個(gè)方法地址 & _mask 生成的索引是一樣的該怎么辦?
源碼(objc-cache.mm)中有處理:
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); // key 為 @selector(test),m 為 _mask
mask_t i = begin;
do {
// 找到索引,返回調(diào)用(IMP)
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
// 若不相等,則使用 cache_next() 方法
} 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);
}
cache_hash 方法:
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask); // 得到索引的 & 運(yùn)算
}
cache_next() 方法(arm64 架構(gòu)):
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask; // 判斷結(jié)果是否為 0
}
緩存方法的時(shí)候:

若 new() 函數(shù)的目標(biāo)索引已經(jīng)有值,則在目標(biāo)索引 -1 的位置緩存,若還有值,則繼續(xù)減 1,當(dāng)結(jié)果為 0 的時(shí)候,則取 _mask 值即哈希表長(zhǎng)度 - 1。
當(dāng)緩存進(jìn)來(lái)一個(gè)方法后緩存方法數(shù)大于 _mask 值后會(huì)調(diào)用 expand() 方法對(duì) _buckets 進(jìn)行擴(kuò)容,然后調(diào)用 reallocate() 方法清空緩存。
并不是每次緩存方法 _mask 都會(huì)變,而是一開(kāi)始就開(kāi)辟容量為 n 的哈希表,不夠用的時(shí)候則再開(kāi)辟容量為 2 倍的哈希表,以此類(lèi)推,如 10,20,40,80,160 ...
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
...
reallocate(oldCapacity, newCapacity);
}
我們用代碼驗(yàn)證如上過(guò)程:
首先新建 Human 類(lèi),有 run 方法,新建 Singer 類(lèi)繼承 Human 類(lèi), 有 sing 方法,新建 BoA 類(lèi)繼承自 Singer 類(lèi),有 dance 方法。
BoA* boa = [[BoA alloc] init];
v_objc_class* boaCls = (__bridge v_objc_class*)[BoA class];
[boa run]; //加斷點(diǎn)
[boa sing]; //加斷點(diǎn)
[boa dance]; //加斷點(diǎn)
NSLog(@"=====end===="); //加斷點(diǎn)
運(yùn)行來(lái)到第一個(gè)斷點(diǎn):

發(fā)現(xiàn)哈希表容量為 4(_mask + 1),此時(shí) _occupied 為 1,緩存的可能是 init 方法。
來(lái)到第二個(gè)斷點(diǎn):

_occupied 為 2,已緩存 run 方法。
來(lái)到第三個(gè)斷點(diǎn):

_occupied 為 3,已緩存 sing 方法。
來(lái)到第四個(gè)斷點(diǎn):

_occupied 為 1,并且哈希表已經(jīng)擴(kuò)容,容量為 8。舊的緩存內(nèi)容全部清空,這個(gè) 1 是緩存的 dance 方法。
objc_msgSend
首先我們將下面的代碼轉(zhuǎn)成 C++ 代碼:
BoA* boa = [[BoA alloc] init];
[boa dance];
得到:
BoA* boa = ((BoA *(*)(id, SEL))(void *)objc_msgSend)((id)((BoA *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BoA"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)boa, sel_registerName("dance"));
[boa dance] 簡(jiǎn)化版后得:
objc_msgSend(boa, sel_registerName("dance"));
這就是我們最熟悉的消息機(jī)制:objc_msgSend 方法。
第二個(gè)參數(shù)為:傳遞一個(gè) C 語(yǔ)言字符串,返回一個(gè) SEL。實(shí)際等價(jià)于
@selector(dance)。
Obejective-C 中的方法調(diào)用,最終都轉(zhuǎn)換成 objc_msgSend 函數(shù)的調(diào)用。
objc_msgSend 的執(zhí)行流程可分為 3 個(gè)階段:
- 消息發(fā)送
- 動(dòng)態(tài)方法解析
- 消息轉(zhuǎn)發(fā)
在執(zhí)行 objc_msgSend 方法的時(shí)候,會(huì)對(duì)給接收者(Receiver)發(fā)送消息,例子中的接收者是對(duì)象 boa,在該階段會(huì)嘗試查找方法進(jìn)行調(diào)用,若能找到,就不會(huì)進(jìn)入動(dòng)態(tài)解析階段,否則則進(jìn)入動(dòng)態(tài)解析階段,該階段允許動(dòng)態(tài)創(chuàng)建新方法,若動(dòng)態(tài)解析階段未做任何操作,則進(jìn)入消息轉(zhuǎn)發(fā)階段,轉(zhuǎn)發(fā)給另外一個(gè)對(duì)象來(lái)調(diào)用,若未找到合適的對(duì)象調(diào)用,則會(huì)報(bào)經(jīng)典的方法找不到的錯(cuò)誤:
unrecognized selector sent to instance xxx.
objc_msgSend 源碼解讀
我們可在 objc-msg-arm64.s 中看到 objc_msgSend 方法的匯編源碼。
看到:
ENTRY _objc_msgSend
ENTRY 是一個(gè)宏,它的定義:
.macro ENTRY /* name */
.text
.align 5
.globl $0
$0:
.endmacro
_objc_msgSend 結(jié)束調(diào)用為:
END_ENTRY _objc_msgSend
中間的部分都是它的實(shí)現(xiàn),這段代碼內(nèi)部做了什么?
首先看到:
cmp p0 #0
b.le LNilOrTagged
該句表示若 p0 小于等于 0 的話(huà) 跳轉(zhuǎn)到 LNilOrTagged 代碼塊。并且這里的 p0 是 objc_msgSend 的第一個(gè)參數(shù),為上述例子中的 boa。
b 為匯編中的跳轉(zhuǎn)指令。le 是小于等于的意思。p0 為寄存器,里面存放的是消息接收者。
在 LNilOrTagged 中看到:
b.eq LReturnZero
在 LReturnZero 中看到:
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
ret 為 return 關(guān)鍵字。
那么該段的意思很明確:若消息接收者為 nil,則退出 objc_msgSend 函數(shù)。
若消息接收者不為空,則會(huì)來(lái)到:
LGetIsaDone:
CacheLookup NORMAL
這句就是方法緩存查找,CacheLookup 也是一個(gè)宏:
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
...
...
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
...
.endmacro
注釋很明顯在表明:該處在計(jì)算索引,然后根據(jù)索引去方法緩存中查找方法。其中:
CacheHit $0
為查找到方法,直接調(diào)用或者返回 IMP。
沒(méi)有查找到則:
CheckMiss $0
CheckMiss 同樣為一個(gè)宏:
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
由于上面?zhèn)鬟f的參數(shù)為 NORMAL,那么我們也只關(guān)注 NORMAL 的部分,即調(diào)用 __objc_msgSend_uncached 方法。該方法內(nèi)部會(huì)調(diào)用 MethodTableLookup,說(shuō)明未在緩存中找到方法則去其他地方查找方法,該方法內(nèi)部:
bl __class_lookupMethodAndLoadCache3
bl 為跳轉(zhuǎn)調(diào)用的指令。
該方法為 C 語(yǔ)言函數(shù),內(nèi)部調(diào)用 lookUpImpOrForward:
lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
obj 為消息接收者,核心的代碼就是 lookUpImpOrForward 方法,核心邏輯為:
...
retry:
runtimeLock.assertLocked();
imp = cache_getImp(cls, sel); // 在執(zhí)行該句之前可能動(dòng)態(tài)添加一些方法,所以需要再檢查一次緩存
if (imp) goto done; // 若找到了,返回 IMP
{
// 未找到,來(lái)到這里
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 找到方法后緩存該方法
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
// 返回 IMP
imp = meth->imp;
goto done;
}
}
// 若還沒(méi)有找到,則去父類(lèi)的方法緩存里去查找
{
unsigned attempts = unreasonableClassCount();
// for 循環(huán)為一層一層向父類(lèi)查找
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
...
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 若查找到方法,則緩存到本類(lèi)當(dāng)中
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
...
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
...
getMethodNoSuper_nolock 為便利 class_rw_t 中的方法列表:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
...
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
search_method_list() 方法中是分條件查找,一個(gè)是查找排好序的方法列表,一個(gè)是查找未排序的方法列表,findMethodInSortedMethodList() 為在已經(jīng)排序的方法列表中查找,其內(nèi)部是二分查找。另一個(gè)則是普通遍歷查找。
最終,消息發(fā)送的流程為:

在 lookUpImpOrForward 的內(nèi)部邏輯中,若如何都沒(méi)有找到方法,會(huì)嘗試動(dòng)態(tài)解析:
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);// 動(dòng)態(tài)解析
runtimeLock.lock();
// 標(biāo)記是否解析過(guò),置為 YES
triedResolver = YES;
goto retry;
}
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
_class_resolveMethod() 方法中:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) { // 判斷是否為元類(lèi)
_class_resolveInstanceMethod(cls, sel, inst);// 內(nèi)部是調(diào)用 objc_msgSend 方法
}
else {
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
_class_resolveInstanceMethod 可以動(dòng)態(tài)的添加方法,我們模擬一下動(dòng)態(tài)解析的過(guò)程,我們首先在 BoA.h 中添加函數(shù)聲明:
- (void)playGolf;
不實(shí)現(xiàn),在外部 [boa playGolf] 的時(shí)候會(huì)報(bào):
'NSInvalidArgumentException', reason: '-[BoA playGolf]: unrecognized selector sent to instance 0x2811b8170'
然后重寫(xiě) resolveInstanceMethod 方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(playGolf)) {
Method method = class_getInstanceMethod(self, @selector(play));
IMP imp = method_getImplementation(method);
class_addMethod(self, sel, imp, "v@:");
}
return YES;
}
play 方法:
- (void)play {
NSLog(@"Play Golf!!!");
}
再次運(yùn)行 [boa playGolf] 則會(huì)打印:
Play Golf!!!
該函數(shù)就是在運(yùn)行時(shí)動(dòng)態(tài)添加的,而非編譯時(shí)期添加的。并且調(diào)用成功后 triedResolver 置為 YES,并且放到 cache 中,下次再調(diào)用則直接走消息轉(zhuǎn)發(fā)的流程。

若消息發(fā)送和動(dòng)態(tài)方法解析階段都沒(méi)有找到方法的實(shí)現(xiàn),則會(huì)進(jìn)入到最后的階段:消息轉(zhuǎn)發(fā)。
進(jìn)入消息轉(zhuǎn)發(fā)階段,底層會(huì)調(diào)用 ___forwarding___ 函數(shù),這個(gè)函數(shù)會(huì)調(diào)用 - (id)forwardingTargetForSelector:(SEL)aSelector 方法,我們可以在該方法內(nèi)讓別的對(duì)象來(lái)調(diào)用 playGolf 函數(shù):
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(playGolf)) {
return [[Valenti alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
Valenti 類(lèi)中聲明并實(shí)現(xiàn)了 playGolf 的方法:
-(void)playGolf {
NSLog(@"Valenti plays golf!!!");
}
運(yùn)行結(jié)果:
Valenti plays golf!!!
若未實(shí)現(xiàn) forwardingTargetForSelector 方法,則會(huì)調(diào)用 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 方法,該方法要求返回一個(gè)方法簽名,然后執(zhí)行 - (void)forwardInvocation:(NSInvocation *)anInvocation 方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(playGolf)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// anInvocation 原方法接收者為 boa 對(duì)象,在這里改成了 Valenti 的對(duì)象
[anInvocation invokeWithTarget:[[Valenti alloc] init]];
}
NSInvocation 中封裝了函數(shù)的調(diào)用,參數(shù),以及方法調(diào)用者。這些信息是由方法簽名決定的。
消息轉(zhuǎn)發(fā)的流程為:

例子中的方法都是誤無(wú)參且無(wú)返回值的,那么有參有返回值的又是什么形式:
假如有 release 方法,該方法是打印「發(fā)布了多少?gòu)垖?zhuān)輯」,需要傳入一個(gè) count 的參數(shù)決定多少?gòu)?,BoA 聲明未實(shí)現(xiàn)該方法, Valenti 中聲明且實(shí)現(xiàn)了該方法:
- (BOOL)release:(int)count {
NSLog(@"Release %d albums!", count);
return count == 0 ? NO : YES;
}
則在消息轉(zhuǎn)發(fā)階段:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(release:)) {
// 只有函數(shù)類(lèi)型的不同
return [NSMethodSignature signatureWithObjCTypes:"B@:i"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[[Valenti alloc] init]];
}
外部調(diào)用 [boa release: 5] 運(yùn)行打印:
Release 5 albums!
我們可以在 forwardInvocation 方法中得到 anInvocation 的返回值和參數(shù)信息:
int param;
[anInvocation getArgument:¶m atIndex:2];
BOOL ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d %d",param, ret);
打印結(jié)果為 5, 1。
[anInvocation getArgument:¶m atIndex:2] 為什么 index 為 2?,因?yàn)閰?shù)順序?yàn)椋簉eceiver、selector 其次才是其他參數(shù)。
以上便是消息機(jī)制的所有內(nèi)容。
super 關(guān)鍵字
理解 super 關(guān)鍵字,還需要借助上面 BoA 的繼承鏈:BoA 繼承 Singer 繼承 Human。
然后在 BoA 的 init() 方法中:
- (instancetype)init {
if (self = [super init]) {
NSLog(@"[super class] %@", [super class]);
NSLog(@"[super superclass] %@", [super superclass]);
}
return self;
}
結(jié)果為:
[super class] BoA
[super superclass] Singer
是不是和猜想有點(diǎn)出入?明明是 super 指針,打印的卻是本類(lèi)以及本類(lèi)的父類(lèi)。
super 關(guān)鍵字底層執(zhí)行的是 objc_msgSendSuper 方法。該方法傳入兩個(gè)參數(shù),一個(gè)是 objc_super 的結(jié)構(gòu)體,源碼中的結(jié)構(gòu)體形式為:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver; // 消息接收者,BoA 對(duì)象
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
第二個(gè)參數(shù)為 SEL。
轉(zhuǎn)成 C++ 代碼后,我們發(fā)現(xiàn)傳入的 objc_super 類(lèi)型的參數(shù)第一個(gè)成員初始化結(jié)果為 self,第二個(gè)為 class_getSuperclass(objc_getClass("BoA")) 也就是 Singer 類(lèi)。
從 objc_super 的結(jié)構(gòu)可以知道,雖然調(diào)用的是 super,但是實(shí)際的消息接收者仍然是 BoA 對(duì)象。那么傳入的父類(lèi)作用是什么?是告訴從哪里開(kāi)始找方法,也就是說(shuō)是從父類(lèi)中找class/superclass 方法,但接收者仍然是本類(lèi)的對(duì)象。