消息轉(zhuǎn)發(fā)
由上文 Class 內(nèi)部結(jié)構(gòu) 可知:
在 objc_msgSend
到查找方法實(shí)現(xiàn) lookUpImpOrNil
的時(shí)候會(huì)先查找 當(dāng)前類 然后 父類(包括緩存列表)一層一層向上查找, 查找不到的時(shí)候就會(huì)進(jìn)入動(dòng)態(tài)方法解析
, 那么動(dòng)態(tài)方法解析
是怎么實(shí)現(xiàn)的?
且看源碼:
1. 動(dòng)態(tài)方法解析
_class_resolveMethod(cls, sel, inst)
:
void _class_resolveMethod(Class cls, SEL sel, id inst {
// 1. 不是元類, 則為實(shí)例方法調(diào)用
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// 2. 類方法動(dòng)態(tài)解析
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
// 3. 當(dāng)步驟 2 沒有處理(即 `+resolveClassMethod`返回值為 NO)時(shí),
// 才會(huì)走到步驟 3.
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
實(shí)例方法動(dòng)態(tài)解析(類對(duì)象
)(步驟 1)
調(diào)用沒有實(shí)現(xiàn)的實(shí)例方法時(shí), 會(huì)在類對(duì)象
中查找動(dòng)態(tài)方法解析實(shí)現(xiàn), 即 :
_class_resolveInstanceMethod(cls, sel, inst)
:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) {
// 檢查元類是否實(shí)現(xiàn) + resolveInstanceMethod 方法, 沒有則直接返回.
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
// Resolver not implemented.
return;
}
// 發(fā)送 + resolveInstanceMethod 消息給當(dāng)前類(類對(duì)象)
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// 如果沒有處理(NSObject 默認(rèn)實(shí)現(xiàn)返回 NO), 則 imp 為空.
// Cache the result (good or bad) so the resolver doesn't fire
// next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
// 打印 log
...
}
}
類方法動(dòng)態(tài)解析(步驟 2)
元類中查找動(dòng)態(tài)方法解析實(shí)現(xiàn).
static void _class_resolveClassMethod(Class cls, SEL sel, id inst) {
// 斷言 cls 為元類.
assert(cls->isMetaClass());
// 檢查 cls 是否有實(shí)現(xiàn) resolveClassMethod 方法, 沒有則直接返回.
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
// Resolver not implemented.
return;
}
// 給 cls 類對(duì)象發(fā)送 + resolveClassMethod 消息
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// 緩存方法的 imp,因?yàn)樵谏厦嬉徊揭呀?jīng)實(shí)現(xiàn)了方法動(dòng)態(tài)解析,
// 這里再作查詢(包含緩存)操作.
// Cache the result (good or bad) so the resolver doesn't fire
// next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
// 打印 log
...
}
}
3. 實(shí)例方法動(dòng)態(tài)解析(元類對(duì)象
)(步驟 3) - 重點(diǎn)難理解
注 : 雖然同步驟 1 的方法相同, 但是當(dāng)前類cls
不一樣, 步驟 1 的當(dāng)前類 cls
為類對(duì)象
, 而步驟 3 的當(dāng)前類 cls
為 元類對(duì)象
, 相當(dāng)于在元類中找 +方法
(至 NSObject 根類找 -方法
) !
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) {
// 檢查元類是否實(shí)現(xiàn) + resolveInstanceMethod 方法, 沒有則直接返回.
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
// Resolver not implemented.
return;
}
/* 發(fā)送 + resolveInstanceMethod 消息給當(dāng)前類(元類!!!)
元類中找 '+ 方法' 操作, 僅在查找 NSObject 元類 '+ 方法'
(查找父類即 NSObject 類對(duì)象的 '+ 方法', 即在 NSObject 元類里的 '- 方法')
時(shí)才能找到(NSObject 默認(rèn)實(shí)現(xiàn)返回 NO, 需要重寫).
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// 如果沒有處理(NSObject 默認(rèn)實(shí)現(xiàn)返回 NO), 則 imp 為空.
// Cache the result (good or bad) so the resolver doesn't fire
// next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
// 打印 log
...
}
}
2. 消息轉(zhuǎn)發(fā)
在以上動(dòng)態(tài)方法解析失敗后, Xcode 便會(huì)告訴你找不到方法:
Terminating app due to uncaught exception 'NSInvalidArgumentException', \
reason: '+[WXPerson wx_classMethod]: unrecognized selector sent to \
class 0x100002720'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff3d77ecf9 __exceptionPreprocess + 256
1 libobjc.A.dylib 0x0000000100357a8f objc_exception_throw + 47
2 CoreFoundation 0x00007fff3d7f8a56 __CFExceptionProem + 0
3 CoreFoundation 0x00007fff3d7210ef ___forwarding___ + 1485
4 CoreFoundation 0x00007fff3d720a98 _CF_forwarding_prep_0 + 120
5 LGTest 0x0000000100001369 main + 57
6 libdyld.dylib 0x00007fff69b403d5 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
發(fā)現(xiàn)調(diào)用棧中的方法都找不到, 蘋果隱藏了消息轉(zhuǎn)發(fā)的實(shí)現(xiàn)?
是的, 蘋果爸爸覺得這個(gè)消息轉(zhuǎn)發(fā)的實(shí)現(xiàn)非常??,于是對(duì)該處實(shí)現(xiàn)閉源了.
對(duì)此, 先根據(jù)已知調(diào)用方法進(jìn)行跟蹤查找, 即在方法動(dòng)態(tài)解析失敗后, 直接調(diào)用 _objc_msgForward_impcache
方法獲取 imp.
那么接下來就查找該方法, 結(jié)果發(fā)現(xiàn)該方法是由匯編實(shí)現(xiàn):
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
從中可以看到跳轉(zhuǎn)到 __objcForward
后調(diào)用了 __objc_forward_handler
:
// 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;
終于知道這報(bào)錯(cuò)出處了...
然后就沒有然后了...
但是! 從報(bào)錯(cuò)的調(diào)用棧中我們?nèi)匀豢梢钥吹较⑥D(zhuǎn)發(fā)調(diào)用了 CoreFoundation 框架, 于是迫不及待的下載 CoreFoundation 源碼進(jìn)行分析:
CoreFoundation 下載鏈接.
經(jīng)過一系列查找仍然找不到想要的方法...
既然我們知道轉(zhuǎn)發(fā)是在 CoreFoundation 中, 那么我們從匯編的角度來看下 CoreFoundation 對(duì)消息轉(zhuǎn)發(fā)到底隱藏了什么:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework
用 Hopper 打開 CoreFoundation 可執(zhí)行文件, 對(duì)其進(jìn)行分析, 由于是在 libobjc 調(diào)用了 objc_defaultForwardHandler
, 所以在 CoreFoundation
中直接查找 ForwardHandler
:
可以看到該 CODE XREF=___CFInitialize+168
, 那么直接進(jìn)入 ___CFInitialize
中查找 objc_setForwardHandler
:
可以看到該函數(shù)注冊(cè)了兩個(gè)回調(diào):
___forwarding_prep_0___
和 ___forwarding_prep_1___
搜索
___forwarding_prep_
關(guān)鍵字發(fā)現(xiàn)公有三個(gè)相似項(xiàng):-
___forwarding_prep_0___
___forwarding_prep_0___.png -
___forwarding_prep_1___
___forwarding_prep_1___.png -
___forwarding_prep_b___
___forwarding_prep_b___.png ____forwarding___
int ____forwarding___(int arg0, int arg1) {
rsi = arg1;
var_30 = *___stack_chk_guard;
r13 = arg0;
rcx = COND_BYTE_SET(NE);
if (rsi != 0x0) {
r12 = _objc_msgSend_stret;
}
else {
r12 = _objc_msgSend;
}
rbx = *(r13 + rcx * 0x8);
var_40 = *(r13 + rcx * 0x8 + 0x8);
r15 = rcx * 0x8;
if (rbx >= 0x0) goto loc_12c9ba;
loc_12c989:
rcx = *_objc_debug_taggedpointer_obfuscator;
rcx = rcx ^ rbx;
rax = rcx >> 0x3c & 0x7;
if (rax == 0x7) {
rax = (rcx >> 0x34 & 0xff) + 0x8;
}
if (rax == 0x0) goto loc_12cd4d;
loc_12c9ba:
var_48 = r15;
r15 = rsi;
var_38 = r12;
r12 = object_getClass(rbx);
var_50 = class_getName(r12);
r14 = @selector(forwardingTargetForSelector:);
if (class_respondsToSelector(r12, r14) == 0x0) goto loc_12ca57;
loc_12c9f2:
rax = _objc_msgSend(rbx, r14);
if ((rax == 0x0) || (rax == rbx)) goto loc_12ca57;
loc_12ca0c:
r12 = var_38;
r15 = var_48;
if (rax >= 0x0) goto loc_12ca4a;
loc_12ca19:
rdx = *_objc_debug_taggedpointer_obfuscator;
rdx = rdx ^ rax;
rcx = rdx >> 0x3c & 0x7;
if (rcx == 0x7) {
rcx = (rdx >> 0x34 & 0xff) + 0x8;
}
if (rcx == 0x0) goto loc_12cd4a;
loc_12ca4a:
*(0x0 + r15) = rax;
r13 = 0x0;
goto loc_12cd7d;
loc_12cd7d:
if (*___stack_chk_guard == var_30) {
rax = r13;
}
else {
rax = __stack_chk_fail();
}
return rax;
loc_12cd4a:
rbx = rax;
goto loc_12cd4d;
loc_12cd4d:
r14 = _getAtomTarget(rbx);
*(r13 + r15) = r14;
___invoking___(r12, r13, r13, 0x400, 0x0);
if (*r13 == r14) {
*r13 = rbx;
}
goto loc_12cd7d;
loc_12ca57:
var_38 = rbx;
if (strncmp(var_50, "_NSZombie_", 0xa) == 0x0) goto loc_12cdbc;
loc_12ca78:
rbx = @selector(methodSignatureForSelector:);
var_48 = r13;
if (class_respondsToSelector(r12, rbx) == 0x0) goto loc_12ce0d;
loc_12ca96:
r12 = var_38;
r14 = _objc_msgSend(r12, rbx);
if (r14 == 0x0) goto loc_12ce6d;
loc_12cab6:
rbx = [r14 _frameDescriptor];
if (((*(int16_t *)(*rbx + 0x22) & 0xffff) >> 0x6 & 0x1) != r15) {
rax = sel_getName(var_40);
rsi = " not";
r8 = "";
rcx = r8;
if ((*(int16_t *)(*rbx + 0x22) & 0xffff & 0x40) == 0x0) {
rcx = rsi;
}
rdx = rax;
if (r15 == 0x0) {
r8 = rsi;
}
_CFLog(0x4, @"*** 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.",
rdx, rcx, r8, r9, stack[2037]);
}
var_50 = rbx;
if (class_respondsToSelector(object_getClass(r12),
@selector(_forwardStackInvocation:)) != 0x0) {
if (*____forwarding___.onceToken != 0xffffffffffffffff) {
dispatch_once(____forwarding___.onceToken, ^ {
/* block implemented at ______forwarding____block_invoke */
});
}
r13 = [NSInvocation requiredStackSizeForSignature:r14];
rdx = *____forwarding___.invClassSize;
r12 = rsp - (rdx + 0xf & 0xfffffffffffffff0);
memset(r12, 0x0, rdx);
objc_constructInstance(*____forwarding___.invClass, r12);
var_40 = r13;
[r12 _initWithMethodSignature:r14
frame:var_48
buffer:r12 - (r13 + 0xf & 0xfffffffffffffff0)
size:r13];
[var_38 _forwardStackInvocation:r12];
r15 = 0x1;
}
else {
rbx = @selector(forwardInvocation:);
if (class_respondsToSelector(object_getClass(r12), rbx) != 0x0) {
rdi = r12;
r12 = [NSInvocation _invocationWithMethodSignature:r14
frame:var_48];
_objc_msgSend(rdi, rbx);
}
else {
r12 = 0x0;
_CFLog(0x4, @"*** NSForwarding: warning:
object %p of class '%s' does not implement
forwardInvocation: -- dropping message",
0x0, object_getClassName(0x0), r8, r9, stack[2037]);
}
var_40 = 0x0;
r15 = 0x0;
}
rax = var_50;
if (r12->_retainedArgs != 0x0) {
rax = *rax;
if (*(int8_t *)(rax + 0x22) < 0x0) {
rdx = *(int32_t *)(rax + 0x1c);
rsi = *(int8_t *)(rax + 0x20) & 0xff;
memmove(*(rsi + var_48 + rdx),
*(rsi + rdx + r12->_frame),
*(int32_t *)(*rax + 0x10));
}
}
rbx = [r14 methodReturnType];
rax = *(int8_t *)rbx;
if ((rax != 0x76) && (((rax != 0x56) ||
(*(int8_t *)(rbx + 0x1) != 0x76)))) {
r13 = r12->_retdata;
if (r15 != 0x0) {
r13 = [[NSData dataWithBytes:r13 length:var_40] bytes];
[r12 release];
rax = *(int8_t *)rbx;
}
if (rax == 0x44) {
asm{ fld tword [r13] };
}
}
else {
r13 = ____forwarding___.placeholder;
if (r15 != 0x0) {
[r12 release];
}
}
goto loc_12cd7d;
loc_12ce6d:
r15 = sel_getName(var_40);
r8 = sel_getUid(r15);
if (r8 != var_40) {
_CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s'
does not match selector known to Objective C runtime
(%p)-- abort", var_40, r15, r8, r9, stack[2037]);
}
rbx = @selector(doesNotRecognizeSelector:);
if (class_respondsToSelector(object_getClass(var_38), rbx) != 0x0) {
rax = _objc_msgSend(var_38, rbx);
asm{ ud2 };
}
else {
rax = _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s'
does not implement doesNotRecognizeSelector:
-- abort", var_38, object_getClassName(var_38),
r8, r9, stack[2037]);
asm{ ud2 };
}
return rax;
loc_12ce0d:
rbx = class_getSuperclass(r12);
r14 = object_getClassName(var_38);
if (rbx == 0x0) {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s'
does not implement methodSignatureForSelector:
-- did you forget to declare the superclass of '%s'?",
var_38, r14, object_getClassName(var_38), r9, stack[2037]);
}
else {
_CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s'
does not implement methodSignatureForSelector:
-- trouble ahead", var_38, r14, r8, r9, stack[2037]);
}
goto loc_12ce6d;
loc_12cdbc:
if (*(int8_t *)___CFOASafe != 0x0) {
___CFRecordAllocationEvent();
}
rax = _CFLog(0x3, @"*** -[%s %s]: message sent to deallocated instance %p",
var_50 + 0xa, sel_getName(var_40), var_38, r9, stack[2037]);
asm{ ud2 };
return rax;
}
Forwarding
內(nèi)邏輯參考: forwarding 或 forwarding
以下引用自 00Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機(jī)制原理(二):
1、先調(diào)用 forwardingTargetForSelector方法獲取新的 target 作為 receiver 重新執(zhí)行 selector,如果返回的內(nèi)容不合法(為 nil或者跟舊 receiver 一樣)魄眉,那就進(jìn)入第二步。
2修赞、調(diào)用 methodSignatureForSelector
獲取方法簽名后,判斷返回類型信息是否正確桑阶,再調(diào)用 forwardInvocation
執(zhí)行 NSInvocation對(duì)象柏副,并將結(jié)果返回。如果對(duì)象沒實(shí)現(xiàn)methodSignatureForSelector
方法蚣录,進(jìn)入第三步割择。
3、調(diào)用 doesNotRecognizeSelector方法萎河。