场景
使用Runtime的消息转发,可以解决方法未实现等原因导致的系统崩溃问题。
了解
查询
当调用某个方法(实例方法/类方法流程基本一致)时:
- 在方法缓存列表中查询,如果找到就执行,如果没有找到继续向下走。
- 在方法列表中查询,如果找到就加入缓存列表,执行;没有就继续向下走。
- 本类没有找到,就去父类中查找(重复1,2操作)。
- 一直找到根类/根元类,如果还没找到,则进入下一阶段。
动态解析
- 当查询步骤中找不到方法的具体实现,下一步就会看类是否实现了resolveInstanceMethod或resolveClassMethod方法。方法默认返回NO,会继续进入下一阶段。如果返回YES,不会走下一步。
代码参考如下:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"here is %s", __func__);
[self jx_addMethodImp:@selector(goImp) toSel:@selector(go) forTarget:[self class]];
return YES;
}
/// 添加方法实现
/// - Parameters:
/// - imp: 方法实现
/// - methodSel: 要实现的方法
/// - class: 类对象
+ (void)jx_addMethodImp:(SEL)imp toSel:(SEL)methodSel forTarget:(Class)class {
// 通过SEL获取Method对象
Method method = class_getInstanceMethod(class, imp);
// 通过method对象获取方法实现
IMP methodImp = method_getImplementation(method);
// 获取方法签名
const char *types = method_getTypeEncoding(method);
// 动态添加方法实现
class_addMethod(class, methodSel, methodImp, types);
}
- (void)goImp {
NSLog(@"this is go function imp");
}
动态给go方法添加一个方法实现goImp。
快速消息转发至其他对象
- +resolveInstanceMethod返回NO,则会走消息转发forwardingTargetForSelector。
⚠️如果resolveInstanceMethod里已经实现了动态添加方法实现,那么即便是返回NO,也不会再走消息转发。
代码实现参考:
#import "JX_PreventingCrashViewController.h"
#import <objc/runtime.h>
@interface JX_ForwardingClass : NSObject
- (void)go;
@end
@implementation JX_ForwardingClass
- (void)go {
NSLog(@"forward target func go");
}
@end
@interface JX_PreventingCrashViewController ()
- (void)go;
@end
@implementation JX_PreventingCrashViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self go];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"here is %s", __func__);
return NO;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [[JX_ForwardingClass alloc] init];
}
@end
- 如果方法返回self或者nil,则不会走转发,继续下一步。
常规消息转发
- 如果不给其他类快速转发,那么执行methodSignatureForSelector方法获取方法签名。
- 如果获取方法签名失败则崩溃。如果获取成功,则执行forwardInvocation做最后拯救。
参考代码如下:
//MARK: - 常规消息转发
/// 获取方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"methodSignatureForSelector");
//查找父类的方法签名
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (signature == nil) {
signature = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return signature;
// return nil;
}
/// 方法签名非nil后执行最后拯救
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation");
// way1: 交给其他对象解决
JX_ForwardingClass *forwardObj = [[JX_ForwardingClass alloc] init];
SEL sel = anInvocation.selector;
if ([forwardObj respondsToSelector:sel]) {
[anInvocation invokeWithTarget:forwardObj];
}
// way2: 通过使用method获取imp解决
// Method method = class_getInstanceMethod([self class], @selector(goImp));
// IMP imp = method_getImplementation(method);
// [anInvocation invokeUsingIMP:imp];
// way3: 通过imp解决
// IMP imp2 = class_getMethodImplementation(self.class, @selector(goImp));
// [anInvocation invokeUsingIMP:imp2];
}
总结
方法调用流程:
- 方法开始调用。
- 查找方法缓存列表。找不到进入3。
- 查找方法列表。找不到进入父类继续执行2、3。如果到根类上还找不到进入4。
- 动态解析:即动态给方法添加一个方法实现。如果没有添加,则进入5。
- 快速消息转发:交给其他对象调用方法。如果不行,进入6。
- 常规消息转发:先获取方法签名,方法签名获取成功后做最后补救。补救可以是交给其他对象调用相应方法,或者是给某个方法添加方法实现。如果最后补救都不可以则进入7。
- 崩溃,找不到方法异常。
其他
没什么了,就先这些吧。