Objective-C
的方法的調用都是一個消息轉發的過程,objc_msgSend(receiver, selector, ...)
,第一個參數表示消息的接收者,第二個參數表示消息的名稱,即方法。
方法的調用,中間經歷了消息的快速查找、慢速查找、消息轉發三個過程。
1.方法快速查找
消息的快速查找在底層通過彙編語言實現,會在cache中依據繼承關係鏈來查找。
如果是實例方法instanceMethod
,則會類的繼承鏈上進行查找,由subclass
->superclass
->rootclass
中查找。如果是類方法classMethod
,則會在元類的繼承鏈上查找,由subclass-meta
->superclass-meta
->rootclass-meta
->rootclass
中查找。
如果快速查找沒有命中緩存,則會進入慢速查找函數lookUpImpOrForward
。
2.方法慢速查找
方法的慢速查找入口在lookUpImpOrForward
,看一下核心代碼:
NEVER_INLINE IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){
IMP imp = nil;
Class curClass = cls;
...
for (;;) {
/*從當前類中查找符合條件的method,如果找到了,則進入done*/
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) { imp = meth->imp(false); goto done;}
/*沒有找到的話,則會向上查找superclass。沒有superclass的話話,則會進入_objc_msgForward_impcache*/
if (slowpath((curClass = curClass->getSuperclass()) == nil)) { imp = forward_imp; break; }
/*優先查找cache,找到則進入done流程,否則繼續慢速查找父類的*/
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) { break; }
if (fastpath(imp)) { goto done; }
}
/*仍然沒有找到:進入動態決議流程*/
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
...
done_unlock:
...
return imp;
}
消息轉發,即動態決議流程,判斷是類方法還是實例方法,來做不同的操作
static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior){
...
if (! cls->isMetaClass()) {
/*如果是類,則執行resolveInstanceMethod*/
resolveInstanceMethod(inst, sel, cls);
}
else {
/*如果是元類,則執行resolveClassMethod*/
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
如若是實例方法則執行+resolveInstanceMethod
,如果是類方法則執行+resolveClassMethod
方法。
static void resolveClassMethod(id inst, SEL sel, Class cls){
/*如果沒有實現+ resolveClassMethod方法則return,因爲在NSObject中實現了該方法,所以不存在找不到的情況??*/
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
return;
}
...
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
//從緩存中查找一遍
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
}
static void resolveInstanceMethod(id inst, SEL sel, Class cls){
/*如果沒有實現+ resolveInstanceMethod方法則return,因爲在NSObject中實現了該方法,所以不存在找不到的情況??*/
if (!lookUpImpOrNilTryCache(cls, @selector(resolveInstanceMethod:), cls->ISA(/*authenticated*/true))) {
return;
}
...
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, @selector(resolveInstanceMethod:), sel);
//從緩存中查找一遍
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
}
2.1 代碼調用未實現的方法
知道系統處理異常的流程後我們就寫代碼試試,首先在NXPerson
的@interface
中申明1個方法,並不實現該方法:
//定義一個NXPerson class
@interface NXPerson : NSObject
- (void)instanceMethod;
@end
@implementation NXPerson
@end
//調用未實現的方法
NXPerson *p = [NXPerson alloc];
[p instanceMethod];
錯誤打印:
調用一個沒有實現的方法,最終crash掉,且拋出錯誤
-[NXPerson instanceMethod]: unrecognized selector sent to instance 0x1011044c0
。
2.2 通過 resolveInstanceMethod
來添加實現
按照源碼中的提供的思路來實現resolveInstanceMethod
方法,我們發現該方法在crash和拋出異常之前被調用了2次,儘管我們只在該方法中調用父類的方法。官方提供該方法的目的在於我們可以在這種情況下,爲一個沒有實現的方法動態添加方法的實現。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s",__func__);
if(sel == NSSelectorFromString(@"instanceMethod")){
//將instanceMethod的實現替換爲run方法的實現
SEL __sel = NSSelectorFromString(@"run");
IMP imp = class_getMethodImplementation(self, __sel);
const char *type = method_getTypeEncoding(class_getInstanceMethod(self, __sel));
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
通過如上代碼,我們將instanceMethod
這個sel
添加了一個實現imp
,再調用[p instanceMethod]
時,系統就會執行run
方法,符合預期,結果如下:
這種方式我們需要保證我們添加的實現是確實存在的。
2.3 通過forwardingTargetForSelector:
指定消息接收者
消息動態決議中還會有第二種補救措施,實現-(id)forwardingTargetForSelector:(SEL)sel
方法,返回能夠消化該sel
的實例即可。
@interface NXTarget : NSObject
- (void)instanceMethod;
@end
@implementation NXTarget
- (void)instanceMethod{
NSLog(@"%s",__func__);
}
@end
測試如下方法,需要將
resolveInstanceMethod
方法屏蔽掉。
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s",__func__);
if(aSelector == NSSelectorFromString(@"instanceMethod")) {
//需要保證NXTarget中有同名的instanceMethod方法
return [[NXTarget alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
通過上面的代碼,我們將名爲instanceMethod
的方法指定了一個新的接收者,這裏需要注意在新的實例中需要有instanceMethod
實例方法,那麼指定爲自己、當前類的實例、返回nil,或者指定的實例中不包含該方法仍然是無效的,會繼續觸發異常。
2.4通過forwardInvocation:
轉發消息
其中methodSignatureForSelector
和forwardInvocation
必須要成堆出現,否則無效。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s",__func__);
if(aSelector == NSSelectorFromString(@"instanceMethod")){
//返回instanceMethod的簽名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s",__func__);
if(anInvocation.selector == NSSelectorFromString(@"instanceMethod")){
//對instanceMethod進行處理,在這裏可以指定消息的接收者`anInvocation.target`,或者指定消息`anInvocation.selector`,通常只需要更改其中之一即可。
//anInvocation.target = [[NXTarget alloc] init];
anInvocation.selector = NSSelectorFromString(@"run");
//執行方法
[anInvocation invoke];
return;
}
[super forwardInvocation:anInvocation];
}
重寫- (void)forwardInvocation:(NSInvocation *)anInvocation
方法,我們可以指定新的消息接收者和消息體,最後調用[anInvocation invoke]
方法將會觸發新的消息發送流程。如果我們只提供了該方法的空實現,那麼原有的方法將得不到執行,也不會拋出異常了。
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s",__func__);
}
綜上所述,在消息查找不到的情況下,有3次亡羊補牢的機會,通過流程圖表述如下:
3.類方法查找流程
類方法的查找流程在這裏不做贅述,與實例方法的查找流程大同效益
1.可以在
+ (BOOL)resolveClassMethod:(SEL)sel
中添加方法,將方法添加到元類
2.可以在+ (id)forwardingTargetForSelector:(SEL)aSelector
中返回類對象。
3.可以在+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
中返回方法的簽名,實現+ (void)forwardInvocation:(NSInvocation *)anInvocation
方法。