如果深入学习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
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #000000 }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b }
span.s1 { color: #ba2da2 }
span.s2 { }
span.s3 { color: #000000 }
span.s4 { color: #3e1e81 }
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"); } @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, "[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