Runtime 系列文章
深入淺出 Runtime(一):初識(shí)
深入淺出 Runtime(二):數(shù)據(jù)結(jié)構(gòu)
深入淺出 Runtime(三):消息機(jī)制
深入淺出 Runtime(四):super 的本質(zhì)
深入淺出 Runtime(五):具體應(yīng)用
深入淺出 Runtime(六):相關(guān)面試題

目錄
- 1. objc_msgSend 方法調(diào)用流程
- 2. 消息發(fā)送
“消息發(fā)送”流程
源碼分析- 3. 動(dòng)態(tài)方法解析
“動(dòng)態(tài)方法解析”流程
源碼分析- 4. 消息轉(zhuǎn)發(fā)
“消息轉(zhuǎn)發(fā)”流程
源碼分析- 總結(jié)
objc_msgSend 執(zhí)行流程圖
1. objc_msgSend 方法調(diào)用流程
- 在
OC中調(diào)用一個(gè)方法時(shí),編譯器會(huì)根據(jù)情況調(diào)用以下函數(shù)中的一個(gè)進(jìn)行消息傳遞:objc_msgSend、objc_msgSend_stret、objc_msgSendSuper、objc_msgSendSuper_stret。當(dāng)方法調(diào)用者為super時(shí)會(huì)調(diào)用objc_msgSendSuper,當(dāng)數(shù)據(jù)結(jié)構(gòu)作為返回值時(shí)會(huì)調(diào)用objc_msgSend_stret或objc_msgSendSuper_stret。其他方法調(diào)用的情況都是轉(zhuǎn)換為objc_msgSend()函數(shù)的調(diào)用。
void objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
- 給
receiver(方法調(diào)用者/消息接收者)發(fā)送一條消息(SEL方法名)
參數(shù) 1 :receiver
參數(shù) 2 :SEL
參數(shù) 3、4、5... :SEL方法的參數(shù) -
objc_msgSend()的執(zhí)行流程可以分為 3 大階段:
消息發(fā)送
動(dòng)態(tài)方法解析
消息轉(zhuǎn)發(fā)
?
2. 消息發(fā)送
“消息發(fā)送”流程


源碼分析
在前面的文章說過,Runtime 是一個(gè)用C、匯編編寫的運(yùn)行時(shí)庫。
在底層匯編里面如果需要調(diào)用 C 函數(shù)的話,蘋果會(huì)為其加一個(gè)下劃線_,
所以查看objc_msgSend函數(shù)的實(shí)現(xiàn),需要搜索_objc_msgSend(objc-msg-arm64.s(objc4))。
// objc-msg-arm64.s(objc4)
/*
_objc_msgSend 函數(shù)實(shí)現(xiàn)
*/
// ??匯編程序入口格式為:ENTRY + 函數(shù)名
ENTRY _objc_msgSend
// ??如果 receiver 為 nil 或者 tagged pointer,執(zhí)行 LNilOrTagged,否則繼續(xù)往下執(zhí)行
cmp x0, #0 // nil check and tagged pointer check
b.le LNilOrTagged
// ??通過 isa 找到 class/meta-class
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
// ??進(jìn)入 cache 緩存查找,傳的參數(shù)為 NORMAL
// CacheLookup 宏,用于在緩存中查找 SEL 對(duì)應(yīng)方法實(shí)現(xiàn)
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LNilOrTagged:
// ??如果 receiver 為 nil,執(zhí)行 LReturnZero,結(jié)束 objc_msgSend
b.eq LReturnZero // nil check
// ??如果 receiver 為 tagged pointer,則執(zhí)行其它
......
b LGetIsaDone
LReturnZero:
ret // 返回
// ??匯編中,函數(shù)的結(jié)束格式為:ENTRY + 函數(shù)名
END_ENTRY _objc_msgSend
.macro CacheLookup
// ??根據(jù) SEL 去哈希表 buckets 中查找方法
// x1 = SEL, x16 = isa
ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
and w12, w1, w11 // x12 = _cmd & mask
add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4)
ldp x9, x17, [x12] // {x9, x17} = *bucket
// ??緩存命中,進(jìn)行 CacheHit 操作
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
// ??緩存中沒有找到,進(jìn)行 CheckMiss 操作
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b // loop
3: // wrap: x12 = first bucket, w11 = mask
add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp x9, x17, [x12] // {x9, x17} = *bucket
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
// CacheLookup NORMAL|GETIMP|LOOKUP
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
.macro CacheHit
.if $0 == NORMAL // ??CacheLookup 傳的參數(shù)是 NORMAL
MESSENGER_END_FAST
br x17 // call imp // ??執(zhí)行函數(shù)
.elseif $0 == GETIMP
mov x0, x17 // return imp
ret
.elseif $0 == LOOKUP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz x9, LGetImpMiss
.elseif $0 == NORMAL // ??CacheLookup 傳的參數(shù)是 NORMAL
cbz x9, __objc_msgSend_uncached // ??執(zhí)行 __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro JumpMiss
.if $0 == GETIMP
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
// ??__objc_msgSend_uncached
// ??緩存中沒有找到方法的實(shí)現(xiàn),接下來去 MethodTableLookup 類的方法列表中查找
STATIC_ENTRY __objc_msgSend_uncached
MethodTableLookup NORMAL
END_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup
blx __class_lookupMethodAndLoadCache3 // ??執(zhí)行C函數(shù) _class_lookupMethodAndLoadCache3
.endmacro
相反,通過匯編中函數(shù)名找對(duì)應(yīng) C 函數(shù)實(shí)現(xiàn)時(shí),需要去掉一個(gè)下劃線_。
// objc-runtime-new.mm(objc4)
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
// ??注意傳參,由于之前已經(jīng)通過匯編去緩存中查找方法,所以這里不會(huì)再次到緩存中查找
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO; // triedResolver 標(biāo)記用于 動(dòng)態(tài)方法解析
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) { // cache = NO,跳過
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
if (!cls->isRealized()) { // ??如果 receiverClass(消息接受者類) 還未實(shí)現(xiàn),就進(jìn)行 realize 操作
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
// ??如果 receiverClass 需要初始化且還未初始化,就進(jìn)行初始化操作
// 這里插入一個(gè) +initialize 方法的知識(shí)點(diǎn)
// 調(diào)用 _class_initialize(cls),該函數(shù)中會(huì)遞歸遍歷父類,判斷父類是否存在且還未初始化 _class_initialize(cls->superclass)
// 調(diào)用 callInitialize(cls) ,給 cls 發(fā)送一條 initialize 消息((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize)
// 所以 +initialize 方法會(huì)在類第一次接收到消息時(shí)調(diào)用
// 調(diào)用方式:objc_msgSend()
// 調(diào)用順序:先調(diào)用父類的 +initialize,再調(diào)用子類的 +initialize (先初始化父類,再初始化子類,每個(gè)類只會(huì)初始化1次)
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
// ??????核心
retry:
runtimeLock.assertReading();
// ??去 receiverClass 的 cache 中查找方法,如果找到 imp 就直接調(diào)用
imp = cache_getImp(cls, sel);
if (imp) goto done;
// ??去 receiverClass 的 class_rw_t 中的方法列表查找方法,如果找到 imp 就調(diào)用并將該方法緩存到 receiverClass 的 cache 中
{
Method meth = getMethodNoSuper_nolock(cls, sel); // ??去目標(biāo)類的方法列表中查找方法實(shí)現(xiàn)
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls); // ??緩存方法
imp = meth->imp;
goto done;
}
}
// ??逐級(jí)查找父類的緩存和方法列表,如果找到 imp 就調(diào)用并將該方法緩存到 receiverClass 的 cache 中
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
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;
}
}
}
// ??進(jìn)入“動(dòng)態(tài)方法解析”階段
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// ??進(jìn)入“消息轉(zhuǎn)發(fā)”階段
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
我們來看一下getMethodNoSuper_nolock(cls, sel)是怎么從類中查找方法實(shí)現(xiàn)的
- 如果方法列表是經(jīng)過排序的,則進(jìn)行二分查找;
- 如果方法列表沒有進(jìn)行排序,則進(jìn)行線性遍歷查找。
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
// ??核心函數(shù) search_method_list()
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
// ??如果方法列表是經(jīng)過排序的,則進(jìn)行二分查找
return findMethodInSortedMethodList(sel, mlist);
} else {
// ??如果方法列表沒有進(jìn)行排序,則進(jìn)行線性遍歷查找
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
......
return nil;
}
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
// ??count >>= 1 二分查找
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
我們來看一下log_and_fill_cache(cls, meth->imp, sel, inst, cls)是怎么緩存方法的
/***********************************************************************
* log_and_fill_cache
* Log this method call. If the logger permits it, fill the method cache.
* cls is the method whose cache should be filled.
* implementer is the class that owns the implementation in question.
**********************************************************************/
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill (cls, sel, imp, receiver);
}
#if TARGET_OS_WIN32 || TARGET_OS_EMBEDDED
# define SUPPORT_MESSAGE_LOGGING 0
#else
# define SUPPORT_MESSAGE_LOGGING 1
#endif
cache_fill()函數(shù)實(shí)現(xiàn)已經(jīng)在上一篇文章中寫到。
關(guān)于緩存查找流程和更多cache_t的知識(shí),可以查看:
深入淺出 Runtime(二):數(shù)據(jù)結(jié)構(gòu)
?
2. 動(dòng)態(tài)方法解析
“動(dòng)態(tài)方法解析”流程

- 如果“消息發(fā)送”階段未找到方法的實(shí)現(xiàn),進(jìn)行一次“動(dòng)態(tài)方法解析”;
- “動(dòng)態(tài)方法解析”后,會(huì)再次進(jìn)入“消息發(fā)送”流程,
從“去 receiverClass 的 cache 中查找方法”這一步開始執(zhí)行。 - 我們可以根據(jù)方法類型(實(shí)例方法 or 類方法)重寫以下方法
+(BOOL)resolveInstanceMethod:(SEL)sel
+(BOOL)resolveClassMethod:(SEL)sel
在方法中調(diào)用以下方法來動(dòng)態(tài)添加方法的實(shí)現(xiàn)
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) - 示例代碼如下,我們分別調(diào)用了 HTPerson 的
eat實(shí)例方法和類方法,而 HTPerson.m 文件中并沒有這兩個(gè)方法的對(duì)應(yīng)實(shí)現(xiàn),我們?yōu)檫@兩個(gè)方法動(dòng)態(tài)添加了實(shí)現(xiàn),輸出結(jié)果如下。
// main.m
#import <Foundation/Foundation.h>
#import "HTPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[HTPerson new] eat];
[HTPerson eat];
}
return 0;
}
@end
// HTPerson.h
#import <Foundation/Foundation.h>
@interface HTPerson : NSObject
- (void)eat; // 沒有對(duì)應(yīng)實(shí)現(xiàn)
- (void)sleep;
+ (void)eat; // 沒有對(duì)應(yīng)實(shí)現(xiàn)
+ (void)sleep;
@end
// HTPerson.m
#import "HTPerson.h"
#import <objc/runtime.h>
@implementation HTPerson
- (void)sleep
{
NSLog(@"%s",__func__);
}
+ (void)sleep
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 獲取其它方法, Method 就是指向 method_t 結(jié)構(gòu)體的指針
Method method = class_getInstanceMethod(self, @selector(sleep));
/*
** 參數(shù)1:給哪個(gè)類添加
** 參數(shù)2:給哪個(gè)方法添加
** 參數(shù)3:方法的實(shí)現(xiàn)地址
** 參數(shù)4:方法的編碼類型
*/
class_addMethod(self, // 實(shí)例方法存放在類對(duì)象中,所以這里要傳入類對(duì)象
sel,
method_getImplementation(method),
method_getTypeEncoding(method)
);
// 返回 YES 代表有動(dòng)態(tài)添加方法實(shí)現(xiàn)
// 從源碼來看,該返回值只是用來打印解析結(jié)果相關(guān)信息,并不影響動(dòng)態(tài)方法解析的結(jié)果
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(eat)) {
Method method = class_getClassMethod(object_getClass(self), @selector(sleep));
class_addMethod(object_getClass(self), // 類方法存放在元類對(duì)象中,所以這里要傳入元類對(duì)象
sel,
method_getImplementation(method),
method_getTypeEncoding(method)
);
return YES;
}
return [super resolveClassMethod:sel];
}
@end
-[HTPerson sleep]
+[HTPerson sleep]
源碼分析
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
......
retry:
......
// ??如果“消息發(fā)送”階段未找到方法的實(shí)現(xiàn),進(jìn)行一次“動(dòng)態(tài)方法解析”
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst); // ??核心函數(shù)
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES; // ??標(biāo)記triedResolver為YES
goto retry; // ??再次進(jìn)入消息發(fā)送,從“去 receiverClass 的 cache 中查找方法”這一步開始
}
// ??進(jìn)入“消息轉(zhuǎn)發(fā)”階段
......
}
// objc-class.mm(objc4)
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
// ??判斷是 class 對(duì)象還是 meta-class 對(duì)象
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// ??核心函數(shù)
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// ??核心函數(shù)
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
// ??查看 receiverClass 的 meta-class 對(duì)象的方法列表里面是否有 SEL_resolveInstanceMethod 函數(shù) imp
// ??也就是看我們是否實(shí)現(xiàn)了 +(BOOL)resolveInstanceMethod:(SEL)sel 方法
// ??這里一定會(huì)找到該方法實(shí)現(xiàn),因?yàn)?NSObject 中有實(shí)現(xiàn)
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// ??如果沒找到,說明程序異常,直接返回
// Resolver not implemented.
return;
}
// ??如果找到了,通過 objc_msgSend 給對(duì)象發(fā)送一條 SEL_resolveInstanceMethod 消息
// ??即調(diào)用一下 +(BOOL)resolveInstanceMethod:(SEL)sel 方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// ??下面是解析結(jié)果的一些打印信息
......
}
/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
// ??查看 receiverClass 的 meta-class 對(duì)象的方法列表里面是否有 SEL_resolveClassMethod 函數(shù) imp
// ??也就是看我們是否實(shí)現(xiàn)了 +(BOOL)resolveClassMethod:(SEL)sel 方法
// ??這里一定會(huì)找到該方法實(shí)現(xiàn),因?yàn)?NSObject 中有實(shí)現(xiàn)
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// ??如果沒找到,說明程序異常,直接返回
// Resolver not implemented.
return;
}
// ??如果找到了,通過 objc_msgSend 給對(duì)象發(fā)送一條 SEL_resolveClassMethod 消息
// ??即調(diào)用一下 +(BOOL)resolveClassMethod:(SEL)sel 方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst), // 該函數(shù)返回值是類對(duì)象,而非元類對(duì)象
SEL_resolveClassMethod, sel);
// ??下面是解析結(jié)果的一些打印信息
......
}
?
3. 消息轉(zhuǎn)發(fā)
“消息轉(zhuǎn)發(fā)”流程

- 如果“消息發(fā)送”階段未找到方法的實(shí)現(xiàn),且通過“動(dòng)態(tài)方法解析”沒有解決,
就進(jìn)入“消息轉(zhuǎn)發(fā)”階段; - “消息轉(zhuǎn)發(fā)”階段分兩步進(jìn)行:Fast forwarding 和 Normal forwarding,顧名思義,第一步速度要比第二步快;
- Fast forwarding:將消息轉(zhuǎn)發(fā)給一個(gè)其它 OC 對(duì)象(找一個(gè)備用接收者),
我們可以重寫以下方法,返回一個(gè)!= receiver的對(duì)象,來完成這一步驟;
+/- (id)forwardingTargetForSelector:(SEL)sel - Normal forwarding:實(shí)現(xiàn)一個(gè)完整的消息轉(zhuǎn)發(fā)過程,
如果上一步?jīng)]能解決未知消息,可以重寫以下兩個(gè)方法啟動(dòng)完整的消息轉(zhuǎn)發(fā)。
?
① 第一個(gè)方法:我們需要在該方法中返回一個(gè)適合該未知消息的方法簽名(方法簽名就是對(duì)返回值類型、參數(shù)類型的描述,可以使用 Type Encodings 編碼,關(guān)于 Type Encodings 可以閱讀我的上一篇 blog 深入淺出 Runtime(二):數(shù)據(jù)結(jié)構(gòu))。
+/- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
Runtime 會(huì)根據(jù)這個(gè)方法簽名,創(chuàng)建一個(gè)NSInvocation對(duì)象(NSInvocation封裝了未知消息的全部?jī)?nèi)容,包括:方法調(diào)用者 target、方法名 selector、方法參數(shù) argument 等),然后調(diào)用第二個(gè)方法并將該NSInvocation對(duì)象作為參數(shù)傳入。
?
② 第二個(gè)方法:我們可以在該方法中:將未知消息轉(zhuǎn)發(fā)給其它對(duì)象;改變未知消息的內(nèi)容(如方法名、方法參數(shù))再轉(zhuǎn)發(fā)給其它對(duì)象;甚至可以定義任何邏輯。
+/- (void)forwardInvocation:(NSInvocation *)invocation
如果第一個(gè)方法中沒有返回方法簽名,或者我們沒有重寫第二個(gè)方法,系統(tǒng)就會(huì)認(rèn)為我們徹底不想處理這個(gè)消息了,這時(shí)候就會(huì)調(diào)用+/- (void)doesNotRecognizeSelector:(SEL)sel方法并拋出經(jīng)典的 crash:unrecognized selector sent to instance/class,結(jié)束 objc_msgSend 的全部流程。
? - 下面我們來看一下這幾個(gè)代碼的默認(rèn)實(shí)現(xiàn)
// NSObject.mm
+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
+ (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
- Fast forwarding 示例代碼如下:
我們調(diào)用了 HTPerson 的eat實(shí)例方法,而 HTPerson.m 文件中并沒有該方法的對(duì)應(yīng)實(shí)現(xiàn),HTDog.m 中有同名方法的實(shí)現(xiàn),我們將消息轉(zhuǎn)發(fā)給 HTDog 的實(shí)例對(duì)象,輸出結(jié)果如下。
// main.m
#import <Foundation/Foundation.h>
#import "HTPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[HTPerson new] eat];
}
return 0;
}
@end
// HTPerson.h
#import <Foundation/Foundation.h>
@interface HTPerson : NSObject
- (void)eat; // 沒有對(duì)應(yīng)實(shí)現(xiàn)
@end
// HTPerson.m
#import "HTPerson.h"
#import "HTDog.h"
@implementation HTPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(eat)) {
return [HTDog new]; // 將 eat 消息轉(zhuǎn)發(fā)給 HTDog 的實(shí)例對(duì)象
// return [HTDog class]; // 還可以將 eat 消息轉(zhuǎn)發(fā)給 HTDog 的類對(duì)象
}
return [super forwardingTargetForSelector:aSelector];
}
@end
// HTDog.m
#import "HTDog.h"
@implementation HTDog
- (void)eat
{
NSLog(@"%s",__func__);
}
+ (void)eat
{
NSLog(@"%s",__func__);
}
@end
-[HTDog eat]
- Normal forwarding 示例代碼及輸出結(jié)果如下:
// HTPerson.m
#import "HTPerson.h"
#import "HTDog.h"
@implementation HTPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(eat)) {
return [[HTDog new] methodSignatureForSelector:aSelector];
//return [NSMethodSignature signatureWithObjCTypes:"v@:i"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 將未知消息轉(zhuǎn)發(fā)給其它對(duì)象
[anInvocation invokeWithTarget:[HTDog new]];
// 改變未知消息的內(nèi)容(如方法名、方法參數(shù))再轉(zhuǎn)發(fā)給其它對(duì)象
/*
anInvocation.selector = @selector(sleep);
anInvocation.target = [HTDog new];
int age;
[anInvocation getArgument:&age atIndex:2]; // 參數(shù)順序:target、selector、other arguments
[anInvocation setArgument:&age atIndex:2]; // 參數(shù)的個(gè)數(shù)由上個(gè)方法返回的方法簽名決定,要注意數(shù)組越界問題
[anInvocation invoke];
int ret;
[anInvocation getReturnValue:&age]; // 獲取返回值
*/
// 定義任何邏輯,如:只打印一句話
/*
NSLog(@"好好學(xué)習(xí)");
*/
}
@end
-[HTDog eat]
源碼分析
// objc-runtime-new.mm(objc4)
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
......
// ??如果“消息發(fā)送”階段未找到方法的實(shí)現(xiàn),且通過“動(dòng)態(tài)方法解析”沒有解決
// ??進(jìn)入“消息轉(zhuǎn)發(fā)”階段
imp = (IMP)_objc_msgForward_impcache; // 進(jìn)入?yún)R編
cache_fill(cls, sel, imp, inst); // 緩存方法
......
}
// objc-msg-arm64.s(objc4)
STATIC_ENTRY __objc_msgForward_impcache
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE // ??執(zhí)行C函數(shù) _objc_forward_handler
ldr x17, [x17, __objc_forward_handler@PAGEOFF]
br x17
END_ENTRY __objc_msgForward
// objc-runtime.mm(objc4)
// Default forward handler halts the process.
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
可以看到_objc_forward_handler是一個(gè)函數(shù)指針,指向objc_defaultForwardHandler(),該函數(shù)只是打印信息。由于蘋果沒有對(duì)此開源,我們無法再深入探索關(guān)于“消息轉(zhuǎn)發(fā)”的詳細(xì)執(zhí)行邏輯。
我們知道,如果調(diào)用一個(gè)沒有實(shí)現(xiàn)的方法,并且沒有進(jìn)行“動(dòng)態(tài)方法解析”和“消息轉(zhuǎn)發(fā)”處理,會(huì)報(bào)經(jīng)典的 crash:unrecognized selector sent to instance/class。我們查看 crash 打印信息的函數(shù)調(diào)用棧,如下,可以看的系統(tǒng)調(diào)用了一個(gè)叫___forwarding___的函數(shù)。

該函數(shù)是 CoreFoundation 框架中的,蘋果對(duì)此函數(shù)尚未開源,我們可以打斷點(diǎn)進(jìn)入該函數(shù)的匯編實(shí)現(xiàn)。

以下是從網(wǎng)上找到的___forewarding___的 C 語言偽代碼實(shí)現(xiàn)。
// 偽代碼
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// ??????調(diào)用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
// ??判斷該方法是否返回了一個(gè)對(duì)象且該對(duì)象 != receiver
if (forwardingTarget && forwardingTarget != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
//??objc_msgSend(返回值, sel, ...);
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 僵尸對(duì)象
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
<breakpoint-interrupt>
}
// ??????調(diào)用 methodSignatureForSelector 獲取方法簽名后再調(diào)用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
// ??調(diào)用 methodSignatureForSelector 獲取方法簽名
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
// ??判斷返回值是否為 nil
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
// ??根據(jù)方法簽名創(chuàng)建一個(gè) NSInvocation 對(duì)象
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
// ??調(diào)用 forwardInvocation
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
SEL *registeredSel = sel_getUid(selName);
// selector 是否已經(jīng)在 Runtime 注冊(cè)過
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // ??????調(diào)用 doesNotRecognizeSelector
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
?
總結(jié)
至此,objc_msgSend方法調(diào)用流程就已經(jīng)講解結(jié)束了。下面來做一個(gè)小總結(jié)。
objc_msgSend 執(zhí)行流程圖
