iOS Runtime之消息转发

场景

使用Runtime的消息转发,可以解决方法未实现等原因导致的系统崩溃问题。

了解

查询

当调用某个方法(实例方法/类方法流程基本一致)时:

  1. 方法缓存列表中查询,如果找到就执行,如果没有找到继续向下走。
  2. 方法列表中查询,如果找到就加入缓存列表,执行;没有就继续向下走。
  3. 本类没有找到,就去父类中查找(重复1,2操作)。
  4. 一直找到根类/根元类,如果还没找到,则进入下一阶段。

动态解析

  1. 当查询步骤中找不到方法的具体实现,下一步就会看类是否实现了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。

快速消息转发至其他对象

  1. +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


  1. 如果方法返回self或者nil,则不会走转发,继续下一步。

常规消息转发

  1. 如果不给其他类快速转发,那么执行methodSignatureForSelector方法获取方法签名。
  2. 如果获取方法签名失败则崩溃。如果获取成功,则执行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];
}


总结

方法调用流程:

  1. 方法开始调用。
  2. 查找方法缓存列表。找不到进入3。
  3. 查找方法列表。找不到进入父类继续执行2、3。如果到根类上还找不到进入4。
  4. 动态解析:即动态给方法添加一个方法实现。如果没有添加,则进入5。
  5. 快速消息转发:交给其他对象调用方法。如果不行,进入6。
  6. 常规消息转发:先获取方法签名,方法签名获取成功后做最后补救。补救可以是交给其他对象调用相应方法,或者是给某个方法添加方法实现。如果最后补救都不可以则进入7。
  7. 崩溃,找不到方法异常。

其他

没什么了,就先这些吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xinxin_0

带我喝杯茶~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值