iOS—消息转发机制

2021-02-24  本文已影响0人  土豆骑士

上文了解了方法的查找流程,当IMP找到不到时,Runtime还给了补救的措施,动态方法决议 和 消息转发(resolve and forwarding)

1:动态方法决议 resolve

实现方法:

#import <objc/message.h>

@implementation LGStudent

- (void)sayHello{
    NSLog(@"替补方法:%s",__func__);
}

+ (void)sayObjc{
       NSLog(@"替补方法:%s",__func__);
}

+ (BOOL)resolveClassMethod:(SEL)sel {
    
    NSLog(@"来了 resolveClassMethod :%s - %@",__func__,NSStringFromSelector(sel));

    
    if (sel == @selector(sayLove)) {
        
        //获取元类 ,添加方法需要添加到 元类里 Meta Class
        Class cls = objc_getMetaClass("LGStudent");
        //获取替代方法的IMP
        IMP imp = class_getMethodImplementation(cls, @selector(sayObjc));
        //获取替代方法的 method
        Method method = class_getClassMethod(cls, @selector(sayObjc));
        //获取替代方法的 encoding type 方法签名
        const char *type = method_getTypeEncoding(method);
        
        return class_addMethod(cls, sel, imp, type);//添加方法到元类里

    }
    
    return [super resolveClassMethod:sel];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    NSLog(@"来了 resolveInstanceMethod :%s - %@",__func__,NSStringFromSelector(sel));

    if (sel == @selector(saySomething)) {
        
        //获取类 ,添加方法需要添加到 元类里 Meta Class
        Class cls = self;
        //获取替代方法的IMP
        IMP sayhimp = class_getMethodImplementation(cls, @selector(sayHello));
        //获取替代方法的 method
        Method sayhmethod = class_getInstanceMethod(cls, @selector(sayHello));
        //获取替代方法的 encoding type 方法签名
        const char *type = method_getTypeEncoding(sayhmethod);
        
        return class_addMethod(cls, sel, sayhimp, type);
        
    }
    
    return [super resolveInstanceMethod:sel];//添加方法到类里
}
@end

源码:runtime-new.mm

/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {
   runtimeLock.assertLocked();
   ASSERT(cls->isRealized());

   runtimeLock.unlock();

   if (! cls->isMetaClass()) {//cls 不是元类
       // try [cls resolveInstanceMethod:sel]
       resolveInstanceMethod(inst, sel, cls);
   } else {//cls 是元类
       // try [nonMetaClass resolveClassMethod:sel]
       // and [cls resolveInstanceMethod:sel]
       resolveClassMethod(inst, sel, cls);
       if (!lookUpImpOrNil(inst, sel, cls)) {//此处有可能去 resolveInstanceMethod 查找
           resolveInstanceMethod(inst, sel, cls);
       }
   }

   // chances are that calling the resolver have populated the cache
   // so attempt using it
   return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

源码中可以看到,调用类方法的话,会到resolveClassMethod去查找是否添加补救的方法,找不到的话,也有可能到resolveInstanceMethod去查找是否添加方法。

猜想:在NSObject的分类里,在resolveInstanceMethod ``resolveClassMethod中可以添加拦截补救方法,适当的可以起到拦截Crash问题,然后让其回归到 首页 我的页面。使用NSStringFromSelector(sel)获取sel 方法名,获取前缀,来拦截。

2: 消息转发Forwarding

在方法查找流程的源代码IMP lookUpImpOrForward()最后,查找IMP找不到时,会有一个imp = (IMP)_objc_msgForward_impcache; //Use forwarding(汇编)。该方法在动态方法决议之后,lldb打印unrecognized selector sent to class 0x100003318之前。

查找该方法踪迹:
1:通过debug汇编断点 的方式我们可以定位到消息转发的重载函数forwardInvocation 和 methodForSelector方法。
2:通过extern void instrumentObjcMessageSends(BOOL flag);打印log的方式可以找到某个方法调用前后,调用了那些方法。
测试上面方法2:

//引用instrumentObjcMessageSends函数
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {

    Person *p = [Person new];
    //开启OC消息日志
    instrumentObjcMessageSends(true);
    //发送消息
    [p sayInstance];
    //关闭OC消息日志
    instrumentObjcMessageSends(false);
    return 0;
}

然后 找到msgSend-,分析日志,路径在/tmp路径下回生成名为msgSend-

//摘取了关键的日志信息
+ Person NSObject resolveInstanceMethod:
+ Person NSObject resolveInstanceMethod:
- Person NSObject forwardingTargetForSelector:
- Person NSObject forwardingTargetForSelector:
- Person NSObject methodSignatureForSelector:
- Person NSObject methodSignatureForSelector:
- Person NSObject class
+ Person NSObject resolveInstanceMethod:
+ Person NSObject resolveInstanceMethod:
- Person NSObject doesNotRecognizeSelector:
- Person NSObject doesNotRecognizeSelector:
- Person NSObject class
......

发现 forwardingTargetForSelector methodSignatureForSelector两个方法,而两个方法是在NSObject中的,在子类中可以override。

PS:知识补充:对象的获取, 地址打印。

     //实例对象
    LGStudent *student = [[LGStudent alloc] init];
    NSLog(@"地址: %p",student);// 实例对象student 地址0x101905400
    //类对象
    NSLog(@"地址: %p",[student class]);// 类对象地址0x100003328
    NSLog(@"地址: %p",[LGStudent class]);// 类对象地址0x100003328
    NSLog(@"地址: %p",[[LGStudent class]class]);// 类对象地址0x100003328
    NSLog(@"地址: %p",objc_getClass("LGStudent"));// 类对象地址0x100003328
    NSLog(@"地址: %p",[objc_getClass("LGStudent") class]);// 类对象地址0x100003328

    //元类对象
    NSLog(@"地址: %p",objc_getMetaClass("LGStudent"));// 元类对象地址0x100003300
    NSLog(@"地址: %p",[objc_getMetaClass("LGStudent") class]);// 元类对象地址0x100003300
上一篇 下一篇

猜你喜欢

热点阅读