????????關于OC的消息轉發(fā)機制摇邦,是大部分面試官在面試過程中經常問到的問題转培。在此我整理了一下我對OC消息轉發(fā)機制的理解炸宵。
? ????? 眾所周知OC的一個對象在發(fā)送消息的時候首先在cache里找钟鸵,如果找不到就在該類的struct objc_method_list列表中去搜索性昭,如果找到則直接調用相關方法的實現渣蜗,如果沒有找到就會通過super_class指針沿著繼承樹向上去搜索屠尊,如果找到就跳轉,如果到了繼承樹的根部(通常為NSObject)還沒有找到袍睡。那就會調用NSObjec的一個方法doesNotRecognizeSelector:知染,這樣就會報unrecognized selector 錯誤。其實在調用doesNotRecognizeSelector:方法之前還會進行消息轉發(fā)---還有三次機會來補救斑胜。也就是常說的OC消息轉發(fā)的三次補救措施控淡。
????????總的來說一個OC消息的發(fā)送會經歷四個階段(該四個階段都是搜索到NSObject再進入下階段)
? ? ? ? 1)先在本類中搜索改方法的實現,如果有則直接調用若果沒有則去父類中搜索直到NSObject止潘,如果NSObject沒有則進入消息轉發(fā)(類的動態(tài)方法解析掺炭、備用接受者對象、完整的消息轉發(fā))凭戴。
????????2)類的動態(tài)方法解析:
? ? ? ? 首先創(chuàng)建SonPerson類涧狮,在ViewController 里面寫
? ????????id person = [[SonPerson alloc]init];
? ? ????[person appendString:@""];
? ? ????注意這里要用id 不然編譯報錯。
????????在該類和父類中沒有找到該方法的實現則會執(zhí)行 +(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法么夫。在+(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法 中利用黑魔法runtime 動態(tài)添加方法實現者冤。
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困后,第四個參數是函數類型。具體設置方法可以看注釋衬廷。
控制臺輸出:
resolveInstanceMethod: appendString:
dynamicAdditionMethodIMP
? ? 2)備用接受者: 在+(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法返回NO的時候進入備用接受者階段 摇予。
? ? 創(chuàng)建一個備用接受者類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:
? ??3)完整的消息轉發(fā):當-(void)forwardInvocation:(NSInvocation*)anInvocation 方法方法nil的時候則進入消息轉發(fā)的最后階段,完整的消息轉發(fā)吗跋。也需要創(chuàng)建一個轉發(fā)對象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];
? ? }
}
/*必須重新這個方法侧戴,消息轉發(fā)機制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象 返回nil上面方法不執(zhí)行*/
- (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 - SionChen/OBJC_SendMsg? 并附消息轉發(fā)一張圖: