Runtime消息转发机制forward

方法查找流程

在这里插入图片描述
我们知道当查找一个方法时,会通过isa指针,从其类中查找,类中没有,则从类的父类查找,当root的类没有这个方法时,就进入了我们的动态解析机制,消息转发。

通过观察lookUpImpOrForward的实现,我们同样可以证实,方法的查询逻辑为对象中没有,就从父类中查找,执行顺序为:

1. 无锁的缓存查找
2. 如果类没有实现(isRealized)或者初始化(isInitialized),实现或者初始化类
3. 加锁
4. 缓存以及当前类中方法的查找
5. 尝试查找父类的缓存以及方法列表
6. 没有找到实现,尝试方法解析器 resolveInstanceMethod:
7. 进行消息转发 forwardingTargetForSeletor:
8. 解锁、返回实现

消息转发机制

在这里插入图片描述
消息转发机制在我的 OC Runtime探索 一文中已经说明,这里讲解下其关键点,以及原理。

消息无法处理最终会响应到doesNotRecognizeSelector:方法中

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}

在这里之前我们可以有3处地方可以防止doesNotRecognizeSelector:的触发,下面依次介绍。


_class_resolveMethod

/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) { //当不是元类
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else { //当是元类时
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

当是元类时,它会走_class_resolveClassMethod方法之外,还会在没有找到的时候,走_class_resolveInstanceMethod方法

假设,当我们没有找到一个类方法,并且我们没有写+resolveClassMethod的时候,怎么办呢?_class_resolveMethod的这段代码告诉我们,还要进行一步补救措施_class_resolveInstanceMethod,即从其元类中查找实例方法。

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) { //当不是元类
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else { //当是元类时
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

意思是不是,无论怎样,都会走_class_resolveInstanceMethod对吧,只要你没有写+resolveClassMethod

/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (__typeof__(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    ......
}

因为_class_resolveInstanceMethod必定会走(不进行额外操作的情况),我们只需要重写我们如何重写resolveInstanceMethod方法,这里使用分类才能重写NSObject。

#import "NSObject+HCResolver.h"
#import <objc/runtime.h>

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@" >> dynamicMethodIMP");
}

@implementation NSObject (LGKC)

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"NSObject+HCResolver == %s",__func__);
    if (sel == @selector(walk)) {
        class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
        return YES;
    }
    return NO;
}
@end

其实我们就只在NSObject实现的基础上加上了自己的逻辑,在NSObject.mm的源码中

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

这样就能针对方法进行处理,这也是处理很多第三方库的报错的常见方法。


和respondsToSelector:关联

根据GNU源码,respondsToSelector:逻辑通过了resolveClassMethod:和resolveInstanceMethod,所以如果处理了resolveClassMethod:和resolveInstanceMethod并返回YES,respondsToSelector:就会返回YES

- (BOOL) respondsToSelector: (SEL)aSelector
{
  Class cls = object_getClass(self);

  if (aSelector == 0)
    {
      if (GSPrivateDefaultsFlag(GSMacOSXCompatible))
	{
	  [NSException raise: NSInvalidArgumentException
		    format: @"%@ null selector given",
	    NSStringFromSelector(_cmd)];
	}
      return NO;
    }

  if (class_respondsToSelector(cls, aSelector))
    {
      return YES;
    }

  if (class_isMetaClass(cls))
    {
      return [(Class)self resolveClassMethod: aSelector];
    }
  else
    {
      return [cls resolveInstanceMethod: aSelector];
    }
}


监听错误避免崩溃

有时候,我们想在出错的时候,监听这个错误,并做自己的处理,如何在不破坏其原有逻辑情况下,做到错误抛出和处理。
我们只能在消息转发的最后forwardInvocation:进行处理,因为如果在前面处理,无法保证在forwardingTargetForSelector:forwardInvocation:是否进行了适配方法的操作。
新建一个NSObject分类NSObject+LGKC

#import "NSObject+LGKC.h"
#import <objc/runtime.h>

void dynamicMethodIMP(id self, SEL sel) {
    NSLog(@" >> dynamicMethodIMP unrecognized selector [%s] sent to %@",sel_getName(sel),self);
}
void swizzleInstanceMethod(Class cls, SEL origSelector, SEL newSelector)
{
    if (!cls) {
        return;
    }
    /* if current class not exist selector, then get super*/
    Method originalMethod = class_getInstanceMethod(cls, origSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
    
    /* add selector if not exist, implement append with method */
    if (class_addMethod(cls,
                        origSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod)) ) {
        /* replace class instance method, added if selector not exist */
        /* for class cluster , it always add new selector here */
        class_replaceMethod(cls,
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
        
    } else {
        /* swizzleMethod maybe belong to super */
        class_replaceMethod(cls,
                            newSelector,
                            class_replaceMethod(cls,
                                                origSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod)),
                            method_getTypeEncoding(originalMethod));
    }
}
@implementation NSObject (LGKC)
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        /* init方法 */
        swizzleInstanceMethod(objc_getClass("NSObject"), @selector(forwardInvocation:), @selector(hookforwardInvocation:));
        /* init方法 */
        swizzleInstanceMethod(objc_getClass("NSObject"), @selector(methodSignatureForSelector:), @selector(hookMethodSignatureForSelector:));
        /* init方法 */
        swizzleInstanceMethod(objc_getMetaClass("NSObject"), @selector(forwardInvocation:), @selector(hookforwardInvocation:));
        /* init方法 */
        swizzleInstanceMethod(objc_getMetaClass("NSObject"), @selector(methodSignatureForSelector:), @selector(hookMethodSignatureForSelector:));
    });
}


- (NSMethodSignature *)hookMethodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature* ms = [self hookMethodSignatureForSelector:aSelector];
    if (!ms) {
        NSString* info = [NSString stringWithFormat:@"NSObjectSafe hookMethodSignatureForSelector [%@] sent to %@", NSStringFromSelector(aSelector), NSStringFromClass(self.class)];
        NSLog(@"%@",info);
        if (class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:))
            != class_getMethodImplementation(self.class, @selector(methodSignatureForSelector:)) ){
            //这里判断下,是否self实现了这个methodSignatureForSelector,实现了我们就不做处理了
            //methodSignatureForSelectorw对应是交换过后的,如果不相等,self里面那还有个methodSignatureForSelector对应一个自己实现的IMP
            return nil;
        }
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [self hookMethodSignatureForSelector:aSelector];
}

- (void)hookforwardInvocation:(NSInvocation *)invocation{
    NSString* info = [NSString stringWithFormat:@"NSObjectSafe unrecognized selector [%@] sent to %@", NSStringFromSelector(invocation.selector), NSStringFromClass(self.class)];
    NSLog(@"%@",info);
    //这个invocation不用触发也行的,这里写你的收集错误日志啥的
}
+ (NSMethodSignature *)hookMethodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature* ms = [self hookMethodSignatureForSelector:aSelector];
    if (!ms) {
        NSString* info = [NSString stringWithFormat:@"NSObjectSafe hookMethodSignatureForSelector [%@] sent to %@", NSStringFromSelector(aSelector), NSStringFromClass(self.class)];
        NSLog(@"%@",info);
        if (class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:))
            != class_getMethodImplementation(self.class, @selector(methodSignatureForSelector:)) ){
            //这里判断下,是否self实现了这个methodSignatureForSelector,实现了我们就不做处理了
            //methodSignatureForSelectorw对应是交换过后的,如果不相等,self里面那还有个methodSignatureForSelector对应一个自己实现的IMP
            return nil;
        }
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [self hookMethodSignatureForSelector:aSelector];
}

+ (void)hookforwardInvocation:(NSInvocation *)invocation{
    NSString* info = [NSString stringWithFormat:@"NSObjectSafe unrecognized selector [%@] sent to %@", NSStringFromSelector(invocation.selector), NSStringFromClass(self.class)];
    NSLog(@"%@",info);
    //这个invocation不用触发也行的,这里写你的收集错误日志啥的
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        [[[LGPerson alloc] init] run];
        [[LGPerson alloc] run];
        [LGPerson walk];
    }
    return 0;
}

2019-07-09 21:11:02.509572+0800 01-Runtime 初探[99399:13947205] NSObjectSafe -hookMethodSignatureForSelector [run] sent to LGPerson
2019-07-09 21:11:02.510199+0800 01-Runtime 初探[99399:13947205] NSObjectSafe -hookforwardInvocation unrecognized selector [run] sent to LGPerson
2019-07-09 21:11:02.510286+0800 01-Runtime 初探[99399:13947205] NSObjectSafe +hookMethodSignatureForSelector [walk] sent to LGPerson
2019-07-09 21:11:02.510329+0800 01-Runtime 初探[99399:13947205] NSObjectSafe +hookforwardInvocation unrecognized selector [walk] sent to LGPerson

这样程序并不会崩溃,也拦截到了导致崩溃的信息

源码网盘地址:https://pan.baidu.com/s/1aASdT6pKX-lFlpgH7o1q6w


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值