众所周知,runtime有三次机会让我们来挽救crash,它们分别是
-
resolveInstanceMethod
或resolveClassMethod
、 -
forwardingTargetForSelector
、 -
methodSignatureForSelector
和forwardInvocation
那在这三步的处理上也大不相同,下面就具体问题具体分析
1.resolveInstanceMethod或resolveClassMethod
objc运行时会调用resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则运行时就会移到下一步,消息转发(Message Forwarding)。
如果在这一步打补丁的话,我们就得需要根据特定的缺失方法来操作,例如,Person对象缺少eat方法,那我们就需要在这个方法里这样做:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == NSSelectorFromString(@"eat")) {
class_addMethod(self, sel, (IMP)myEat, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void myEat (id self, SEL _cmd, NSString *str) {
NSLog(@"动态添加Eat方法");
}
这样做其实局限性还是很大的,因为苹果已经禁用了热更新,所以当你知道这里有bug时,应该是在业务代码里把这个问题解决掉,而不是通过runtime,而且在这里操作的话,if判断会执行太多次,不方便。
2.forwardingTargetForSelector
forwardingTargetForSelector
是runtime消息传发的第一步,当resolveInstanceMethod
没处理,并且没有找到方法时现时,runtime就会自行resolveInstanceMethod
,在这里的做法如下:
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == NSSelectorFromString(@"eat")) {
ErrorPerson *person = [[ErrorPerson alloc] init];
return person;
}
return [super forwardingTargetForSelector:aSelector];
}
在这里操作其实就是将Person对象的eat方法让ErrorPerson对象执行,当然ErrorPerson也必须得实现了eat方法,不然也会crash
3.methodSignatureForSelector和forwardInvocation
这两个方法要组合起来用,首先在methodSignatureForSelector
判断当前执行的方法签名是否存在,存在则交给父类处理,不存在,则手动注册一个方法签名,当返回注册的新方法签名后,马上就会开始执行forwardInvocation
,然后我们再将事件转移输出log
或者什么都不做也可以,但是必须实现forwardInvocation
方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
return [NSMethodSignature signatureWithObjCTypes:"v@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[self log];
}
- (void)log {
NSLog(@"forwardInvocation");
}