關於OC的消息轉發機制,是大部分面試官在面試過程當中常常問到的問題。在此我整理了一下我對OC消息轉發機制的理解。git
衆所周知OC的一個對象在發送消息的時候首先在該類的struct objc_method_list列表中去搜索,若是找到則直接調用相關方法的實現,若是沒有找到就會經過super_class指針沿着繼承樹向上去搜索,若是找到就跳轉,若是到了繼承樹的根部(一般爲NSObject)尚未找到。那就會調用NSObjec的一個方法doesNotRecognizeSelector:,這樣就會報unrecognized selector 錯誤。其實在調用doesNotRecognizeSelector:方法以前還會進行消息轉發---還有三次機會來補救。也就是常說的OC消息轉發的三次補救措施。github
總的來講一個OC消息的發送會經歷四個階段(該四個階段都是搜索到NSObject再進入下階段)面試
1)先在本類中搜索改方法的實現,若是有則直接調用若果沒有則去父類中搜索直到NSObject,若是NSObject沒有則進入消息轉發(類的動態方法解析、備用接受者對象、完整的消息轉發)。bash
2)類的動態方法解析:app
首先建立SonPerson類,在ViewController 裏面寫函數
id person = [[SonPerson alloc]init];
[person appendString:@""];
複製代碼
注意這裏要用id 否則編譯報錯。ui
在該類和父類中沒有找到該方法的實現則會執行 +(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法。在+(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法 中利用黑魔法runtime 動態添加方法實現。spa
void dynamicAdditionMethodIMP(id self,SEL _cmd){
NSLog(@"dynamicAdditionMethodIMP");
}
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"resolveInstanceMethod: %@", NSStringFromSelector(sel));
if(sel ==@selector(appendString:)) {
class_addMethod([selfclass], sel, (IMP)dynamicAdditionMethodIMP,"v@:");
returnYES;
}
return[superresolveClassMethod:sel];
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"resolveInstanceMethod: %@", NSStringFromSelector(sel));
if(sel ==@selector(appendString:)) {
class_addMethod([selfclass], sel, (IMP)dynamicAdditionMethodIMP,"v@:");
returnYES;
}
return[super resolveInstanceMethod:sel];
}
BOOL class_addMethod(Class cls, SEL name, IMP imp,constchar*types);
複製代碼
第一個參數是須要添加方法的類,第二個參數是一個selector,也就是實例方法的名字,第三個參數是一個IMP類型的變量也就是函數實現,須要傳入一個C函數,這個函數至少有兩個參數,一個是id self一個是SEL _cmd,第四個參數是函數類型。具體設置方法能夠看註釋。指針
控制檯輸出:code
resolveInstanceMethod: appendString:
dynamicAdditionMethodIMP
3)備用接受者: 在+(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法返回NO的時候進入備用接受者階段 。
建立一個備用接受者類ForwardPerson 實現appendString:方法
-(void)appendString:(NSString*)str{
NSLog(@"%@===%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
}
複製代碼
在SonPerson類中實現- (id)forwardingTargetForSelector:(SEL)aSelector 方法 並返回一個備用接受者對象
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"forwardingTargetForSelector");
return [ForwardPerson new];
}
複製代碼
控制檯輸出:
forwardingTargetForSelector
ForwardPerson===appendString:
4)完整的消息轉發:當-(void)forwardInvocation:(NSInvocation*)anInvocation 方法方法nil的時候則進入消息轉發的最後階段,完整的消息轉發。也須要建立一個轉發對象ForwardInvocation
#import "ForwardInvocation.h"
@implementationForwardInvocation
-(void)appendString:(NSString*)str{
NSLog(@"%@===%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
}
@end
複製代碼
在SonPerson中實現-(void)forwardInvocation:(NSInvocation*)anInvocation和- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector方法
-(void)forwardInvocation:(NSInvocation*)anInvocation{
NSLog(@"forwardInvocation");
if ([ForwardInvocation instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self.invocation];
}
}
/*必須從新這個方法,消息轉發機制使用從這個方法中獲取的信息來建立NSInvocation對象 返回nil上面方法不執行*/
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature*signature = [super methodSignatureForSelector:aSelector];
if(!signature){
if ([ForwardInvocation instancesRespondToSelector:aSelector]){
signature = [ForwardInvocation instanceMethodSignatureForSelector:aSelector];
}
}
returnsignature;
}
複製代碼
控制檯輸出:
forwardInvocation
ForwardInvocation===appendString:
最後附Demo:github.com/SionChen/OB…
並附消息轉發一張圖: