標籤:tar targe method 建立 ng2 end type nbsp ret
如果深入學習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, "[email protected]:"); 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");}@endvoid 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, "[email protected]:"); 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訊息轉寄學習筆記