接上篇內容,若是方法的調用,沒有實現動態方法決議,那麼就會進行消息轉發。消費轉發分爲兩個階段分別以下:sass
要實現快速轉發須要在未實現實現動態方法決議的狀況下,實現下面函數:markdown
//將方法的調用轉給其餘對象(本身作不了,就交給其餘人作 )
-(id)forwardingTargetForSelector:(SEL)aSelector
+(id)forwardingTargetForSelector:(SEL)aSelector
複製代碼
代碼演示:函數
#import <Foundation/Foundation.h>
@interface ABPerson : NSObject
@end
@implementation ABPerson
- (void)doSomething
{
NSLog(@"%s",__func__);
}
@end
@interface ABPeople : NSObject
- (void)doSomething;
@end
@implementation ABPeople
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
return [ABPerson alloc];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ABPeople *p = [ABPeople new];
[p doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
複製代碼
ABPeople
沒有實現的方法交給了ABPerson
實現並調用。可是若是快速轉發也沒有實現呢?那就進行慢速轉發。post
//經過方法編號,返回方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
複製代碼
//調用者Target,方法sel都被保存在anInvocation中
-(void)forwardInvocation:(NSInvocation *)anInvocation
+(void)forwardInvocation:(NSInvocation *)anInvocation
複製代碼
這兩個方法必須同時實現,forwardInvocation
能夠實現空方法,也不會報錯。ui
代碼演示:spa
#import <Foundation/Foundation.h>
@interface ABPeople : NSObject
- (void)doSomething;
@end
@implementation ABPeople
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(doSomething)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"%@- %@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
ABPerson *p = [ABPerson new];
if ([p respondsToSelector:anInvocation.selector]) {
anInvocation.target = p;
[anInvocation invoke];
}
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ABPeople *p = [ABPeople new];
[p doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
複製代碼
在方法找不到,崩潰的時候,咱們經過bt
命令打印調用棧:調試
在找不到方法的最後處理doesNotRecognizeSelector
,執行了紅框中的東西,能看出它們來自CoreFoundation
。code
將CoreFoundation
可執行文件拖到Hopper Disassemalber
,查看反彙編僞代碼。orm
全局搜索forwarding
對象
___forwarding_prep_0___
調用了 ____forwarding___
,查看 ____forwarding___
實現:
int ____forwarding___(int arg0, int arg1) {
省略部分僞代碼
loc_649bb:
var_148 = r13;
var_138 = r12;
var_158 = rsi;
rax = object_getClass(rbx);
r12 = rax;
r13 = class_getName(rax);
//當類沒有實現forwardingTargetForSelector,就執行loc_64a67
if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;
loc_649fc:
rdi = rbx;
//當類實現了forwardingTargetForSelector,就返回rax(對象)
rax = [rdi forwardingTargetForSelector:var_140];
//若是rax不存在或等於原來的對象就就執行loc_64a67
if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;
省略部分僞代碼
loc_64a67:
var_138 = rbx;
//判斷是不是殭屍對象
if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;
loc_64a8a:
//當類沒有實現methodSignatureForSelector,就執行loc_64dd7
rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
r14 = var_138;
var_148 = r15;
if (rax == 0x0) goto loc_64dd7;
loc_64ab2:
//當類實現了methodSignatureForSelector,就返回rax(對象)
rax = [r14 methodSignatureForSelector:var_140];
rbx = var_158;
//沒有實現就執行loc_64e3c
if (rax == 0x0) goto loc_64e3c;
省略部分僞代碼
//系統實現的方法_forwardStackInvocation,並無對外暴露
rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
var_150 = r13;
if (rax == 0x0) goto loc_64c19;
loc_64b6c:
if (*0x5c2700 != 0xffffffffffffffff) {
dispatch_once(0x5c2700, ^ {/* block implemented at ______forwarding____block_invoke */ } });
}
//NSInvocation的一些處理
r15 = [NSInvocation requiredStackSizeForSignature:r12];
rsi = *0x5c26f8;
rsp = rsp - ___chkstk_darwin(@class(NSInvocation), rsi, r12, rcx);
r13 = &stack[-360];
__bzero(r13, rsi);
___chkstk_darwin(r13, rsi, r12, rcx);
rax = objc_constructInstance(*0x5c26f0, r13);
var_140 = r15;
[r13 _initWithMethodSignature:r12 frame:var_148 buffer:&stack[-360] size:r15];
[var_138 _forwardStackInvocation:r13];
r14 = 0x1;
goto loc_64c76;
省略部分僞代碼
loc_64c19:
//類是否能響應forwardInvocation,不能響應就執行loc_64ec2
if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;
loc_64c3b:
rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
r13 = rax;
//開始調用
[r14 forwardInvocation:rax];
var_140 = 0x0;
r14 = 0x0;
goto loc_64c76;
loc_64ec2:
rdi = &var_130;
____forwarding___.cold.3(rdi, r14);
goto loc_64ed1;
複製代碼
消息轉發流程(經過sel
尋找imp
)以下:
lookUpImpOrForward
慢速查找沒有找到imp
resolveMethod_locked
判斷當前class
是否是類resolveClassMethod
執行類動態方法決議resolveInstanceMethod
執行對象動態方法決議forwardingTargetForSelector
methodSignatureForSelector
返回簽名,forwardInvocation
調用執行doesNotRecognizeSelector
當方法爲實現時,動態方法決議會打印兩遍
objc
工程調試:
在此文件函數內打上斷點
在第二次即將輸出當前sel
的時候,打印函數調用棧
緣由是CoreFoundation
調用了__forwarding_prep_0___
,通過系統處理調用了class_respondsToSelector_inst
。調用流程以下:
__forwarding_prep_0___
->class_respondsToSelector_inst
->lookUpImpOrNilTryCache
->lookUpImpOrForward
。
這樣作的目的是在消息轉發過程當中,若是imp
被加上了,能夠再次獲得處理。