iOS消息轉發學習筆記
如果深入學習ios Runtime,不得不提到消息轉發,很多框架的實現都基於這一功能實現(例如JSPatch)
雖然看了很多篇關於消息轉發的文章,但是理解的不是很透徹,還是自己實踐一些理解能更加透徹一下。
首先我自己定義了一個MyString繼承NSString
@interface MyString : NSString @end @implementation MyString @end
然後創建一個MyString,通過performSelector調用MissMethod,MissMethod1,MissMethod2等方法。
- (void)testForward { MyString*str = [[MyString alloc] init]; [str performSelector:@selector(MissMethod) withObject:nil]; [str performSelector:@selector(MissMethod2) withObject:nil]; [str performSelector:@selector(MissMethod3) withObject:nil]; }
如果什麽都不寫,這樣肯定會crash,會出現這個錯誤[NSObject(NSObject) doesNotRecognizeSelector:],因為MyString沒有這三個方法,而且父類也沒有。
如果沒有找到方法,系統會嘗試進行補救,看看有沒有能處理的能力,首先會調用resolveInstanceMethod這個方法,這個方法默認是返回NO,走一下步流程,如果返回YES,例如下面方法的實現,如果傳入的sel的名稱是MissMethod開頭,則認為我們的類是可以處理這個方法的。而且如果是MissMethod方法,我們就給這個類添加一個方法dynamicMethodIMP,作為MissMethod的實現。此時當外界調用MissMethod時,其實相當於調用dynamicMethodIMP
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@" >> dynamicMethodIMP");
}
+ (BOOL)resolveInstanceMethod:(SEL)name { NSLog(@" >> Instance resolving %@", NSStringFromSelector(name)); NSString *selName = NSStringFromSelector(name); if ([selName hasPrefix:@"MissMethod"]) { if (name == @selector(MissMethod)) { class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:"); return YES; } else { return NO; } } return [super resolveInstanceMethod:name]; }
如果resolveInstanceMethod方法返回NO了,下面怎麽辦?首先我定義了一個類MyString2作為接盤俠,實現了MissMethod1,MissMethod2方法
@interface MyString2 : NSString @end @implementation MyString2 - (void)MissMethod2 { NSLog(@"MissMethod2"); } - (void)MissMethod3 { NSLog(@"MissMethod3"); } @end
當resolveInstanceMethod方法返回NO了,系統會嘗試調用下面方法,看看有沒有接盤俠來接這個鍋,_str2是MyString2的一個示例,而且實現了MissMethod2方法。當MyString的示例調用MissMethod2方法,MissMethod2->resolveInstanceMethod(NO)->forwardingTargetForSelector,返回一個接盤俠去給這個示例發送消息objc_sendmsg(_str2,sel)
- (id)forwardingTargetForSelector:(SEL)sel { if(sel == @selector(MissMethod2)){ return _str2; } return [super forwardingTargetForSelector:sel]; }
當forwardingTargetForSelector也無法處理,返回nil時,下面會走到這個方法methodSignatureForSelector,判斷sig是否為nil,如果不為nil會走forwardInvocation,最後調用forwardInvocation,如果這個方法也沒有處理,做了最後嘗試之後也就會拋出那個異常doesNotRecognizeSelector
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { NSMethodSignature *sig; sig = [_str2 methodSignatureForSelector:sel]; if (sig) { return sig; } return [super methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { id target = nil; if ([_str2 methodSignatureForSelector:[invocation selector]] ) { target = _str2; [invocation invokeWithTarget:target]; } }
總結一下消息轉發的整個流程,大致流程MissMethod->resolveInstanceMethod(NO)->forwardingTargetForSelector(nil)->methodSignatureForSelector(sig)->forwardInvocation。
下面是完整代碼實現:
// // MyString.m // objc // // Created by zilong.li on 2017/8/17. // // #import "MyString.h" #import <objc/runtime.h> @interface MyString2 : NSString @end @implementation MyString2 - (void)MissMethod2 { NSLog(@"MissMethod2"); } - (void)MissMethod3 { NSLog(@"MissMethod3"); } @end void dynamicMethodIMP(id self, SEL _cmd) { NSLog(@" >> dynamicMethodIMP"); } @interface MyString () { MyString2 *_str2; } @end @implementation MyString - (instancetype)init { self = [super init]; if (self) { _str2 = [[MyString2 alloc] init]; } return self; } + (BOOL)resolveInstanceMethod:(SEL)name { NSLog(@" >> Instance resolving %@", NSStringFromSelector(name)); NSString *selName = NSStringFromSelector(name); if ([selName hasPrefix:@"MissMethod"]) { if (name == @selector(MissMethod)) { class_addMethod([self class], name, (IMP)dynamicMethodIMP, "v@:"); return YES; } else { return NO; } } return [super resolveInstanceMethod:name]; } - (id)forwardingTargetForSelector:(SEL)sel { if(sel == @selector(MissMethod2)){ return _str2; } return [super forwardingTargetForSelector:sel]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { NSMethodSignature *sig; sig = [_str2 methodSignatureForSelector:sel]; if (sig) { return sig; } return [super methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { id target = nil; if ([_str2 methodSignatureForSelector:[invocation selector]] ) { target = _str2; [invocation invokeWithTarget:target]; } } @end
iOS消息轉發學習筆記