????????關(guān)于OC的消息轉(zhuǎn)發(fā)機制,是大部分面試官在面試過程中經(jīng)常問到的問題。在此我整理了一下我對OC消息轉(zhuǎn)發(fā)機制的理解。
? ????? 眾所周知OC的一個對象在發(fā)送消息的時候首先在cache里找,如果找不到就在該類的struct objc_method_list列表中去搜索,如果找到則直接調(diào)用相關(guān)方法的實現(xiàn),如果沒有找到就會通過super_class指針沿著繼承樹向上去搜索,如果找到就跳轉(zhuǎn),如果到了繼承樹的根部(通常為NSObject)還沒有找到。那就會調(diào)用NSObjec的一個方法doesNotRecognizeSelector:,這樣就會報unrecognized selector 錯誤。其實在調(diào)用doesNotRecognizeSelector:方法之前還會進行消息轉(zhuǎn)發(fā)---還有三次機會來補救。也就是常說的OC消息轉(zhuǎn)發(fā)的三次補救措施。
????????總的來說一個OC消息的發(fā)送會經(jīng)歷四個階段(該四個階段都是搜索到NSObject再進入下階段)
? ? ? ? 1)先在本類中搜索改方法的實現(xiàn),如果有則直接調(diào)用若果沒有則去父類中搜索直到NSObject,如果NSObject沒有則進入消息轉(zhuǎn)發(fā)(類的動態(tài)方法解析、備用接受者對象、完整的消息轉(zhuǎn)發(fā))。
????????2)類的動態(tài)方法解析:
? ? ? ? 首先創(chuàng)建SonPerson類,在ViewController 里面寫
? ????????id person = [[SonPerson alloc]init];
? ? ????[person appendString:@""];
? ? ????注意這里要用id 不然編譯報錯。
????????在該類和父類中沒有找到該方法的實現(xiàn)則會執(zhí)行 +(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法。在+(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法 中利用黑魔法runtime 動態(tài)添加方法實現(xiàn)。
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);
第一個參數(shù)是需要添加方法的類,第二個參數(shù)是一個selector,也就是實例方法的名字,第三個參數(shù)是一個IMP類型的變量也就是函數(shù)實現(xiàn),需要傳入一個C函數(shù),這個函數(shù)至少有兩個參數(shù),一個是id self一個是SEL _cmd,第四個參數(shù)是函數(shù)類型。具體設(shè)置方法可以看注釋。
控制臺輸出:
resolveInstanceMethod: appendString:
dynamicAdditionMethodIMP
? ? 2)備用接受者: 在+(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法返回NO的時候進入備用接受者階段 。
? ? 創(chuàng)建一個備用接受者類ForwardPerson 實現(xiàn)appendString:方法
????-(void)appendString:(NSString*)str{
? ? ????NSLog(@"%@===%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
????}
? ? 在SonPerson類中實現(xiàn)- (id)forwardingTargetForSelector:(SEL)aSelector 方法 并返回一個備用接受者對象?
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"forwardingTargetForSelector");
return [ForwardPerson new];
}
控制臺輸出:
forwardingTargetForSelector
?ForwardPerson===appendString:
? ??3)完整的消息轉(zhuǎn)發(fā):當-(void)forwardInvocation:(NSInvocation*)anInvocation 方法方法nil的時候則進入消息轉(zhuǎn)發(fā)的最后階段,完整的消息轉(zhuǎn)發(fā)。也需要創(chuàng)建一個轉(zhuǎn)發(fā)對象ForwardInvocation?
#import "ForwardInvocation.h"
@implementationForwardInvocation
-(void)appendString:(NSString*)str{
? ? NSLog(@"%@===%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
}
@end
? ? 在SonPerson中實現(xiàn)-(void)forwardInvocation:(NSInvocation*)anInvocation和- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector方法
-(void)forwardInvocation:(NSInvocation*)anInvocation{
? ? NSLog(@"forwardInvocation");
? ? if ([ForwardInvocation instancesRespondToSelector:anInvocation.selector]) {
? ? ? ? [anInvocation invokeWithTarget:self.invocation];
? ? }
}
/*必須重新這個方法,消息轉(zhuǎn)發(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? 并附消息轉(zhuǎn)發(fā)一張圖:
