消息转发流程分析

简介: 1. 先来看下动态方法决议的分析;2. 通过instrumentObjcMessageSends来辅助分析;3. 有关反汇编;

总纲领: OC底层探寻


1. 先来看下动态方法决议的分析


static NEVER_INLINE IMP  
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)  
{  
    runtimeLock.assertLocked();  
    ASSERT(cls->isRealized());  
    runtimeLock.unlock();  
    // 动态方法决议 : 给一次机会 重新查询  
    if (! cls->isMetaClass()) {  // 对象 - 类  
        // try [cls resolveInstanceMethod:sel]  
        resolveInstanceMethod(inst, sel, cls);  
    } 
    else { // 类方法 - 元类  
        // try [nonMetaClass resolveClassMethod:sel]  
        // and [cls resolveInstanceMethod:sel]  
        resolveClassMethod(inst, sel, cls);  
        if (!lookUpImpOrNil(inst, sel, cls)) {  // 为什么要有这行代码  ,因为类其实是元类的对象. 所以又执行了对象方法的查找流程
            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);  
}


2. 通过instrumentObjcMessageSends来辅助分析


通过instrumentObjcMessageSends来分析的代码如下:  (最终打印的日志在/private/tmp/msgSends路径下)


#import <Foundation/Foundation.h>  
#import "LGPerson.h"  
// 慢速查找   
extern void instrumentObjcMessageSends(BOOL flag);  
int main(int argc, const char * argv[]) {  
    @autoreleasepool {  
        LGPerson *person = [LGPerson alloc];  
        instrumentObjcMessageSends(YES);  
        [person sayHello];  
        instrumentObjcMessageSends(NO);  
        NSLog(@"Hello, World!");  
    }  
    return 0;  
}


我们可以看到日志的打印文件输出为:


+ LGPerson NSObject resolveInstanceMethod:  
+ LGPerson NSObject resolveInstanceMethod:  
- LGPerson NSObject forwardingTargetForSelector:  
- LGPerson NSObject forwardingTargetForSelector:  
- LGPerson NSObject methodSignatureForSelector:  
- LGPerson NSObject methodSignatureForSelector:  
- LGPerson NSObject class  
+ LGPerson NSObject resolveInstanceMethod:  
+ LGPerson NSObject resolveInstanceMethod:  
- LGPerson NSObject doesNotRecognizeSelector:  
- LGPerson NSObject doesNotRecognizeSelector:  
- LGPerson NSObject class  
- OS_xpc_serializer OS_xpc_object dealloc  
- OS_object NSObject dealloc  
+ OS_xpc_payload NSObject class  
- OS_xpc_payload OS_xpc_payload dealloc  
- NSObject NSObject dealloc  
- OS_dispatch_mach_msg OS_dispatch_object dealloc  
- OS_xpc_dictionary OS_xpc_object dealloc  
+ OS_xpc_mach_send NSObject initialize  
- OS_xpc_mach_send OS_xpc_object dealloc  
- OS_object NSObject dealloc  
- OS_object NSObject dealloc


我们通过打印可以看到, 方法在找不到之后, 又依次运行了resolveInstanceMethod -> forwardingTargetForSelector -> methodSignatureForSelector


那么第一次补救, 如果是对象方法, 可以在resolveInstanceMethod中进行如下的举例替换


+ (BOOL)resolveInstanceMethod:(SEL)sel{  
    NSLog(@"%@ 来了",NSStringFromSelector(sel));  
    if (sel == @selector(say666)) {  
        NSLog(@"%@ 来了",NSStringFromSelector(sel));  
        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));  
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));  
        const char *type  = method_getTypeEncoding(sayMMethod);  
        return class_addMethod(self, sel, imp, type);  
    }
    return [super resolveInstanceMethod:sel];  
}


如果是类方法, 那么根据第一段代码, 是要在resolveClassMethod中进行拦截修正的, 举例代码如下:


+ (BOOL)resolveClassMethod:(SEL)sel{  
    NSLog(@"%@ 来了",NSStringFromSelector(sel));  
    if (sel == @selector(sayNB)) {  
        IMP imp           = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));  
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));  
        const char *type  = method_getTypeEncoding(sayMMethod);  
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);  
    }  
    return [super resolveClassMethod:sel];  
}


当然你也可以合并拦截, 不过要在基类NSObject中的分类中实现, 代码可以如下, 不过这种方法比较暴力, 不建议使用:


#import "NSObject+LG.h"  
#import <objc/message.h>  
@implementation NSObject (LG)  
// 调用方法的时候 - 分类  
+ (BOOL)resolveInstanceMethod:(SEL)sel{  
    NSLog(@"%@ 来了",NSStringFromSelector(sel));  
    if (sel == @selector(say666)) {  
        NSLog(@"%@ 来了",NSStringFromSelector(sel));  
        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));  
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));  
        const char *type  = method_getTypeEncoding(sayMMethod);  
        return class_addMethod(self, sel, imp, type);  
    }  
    else if (sel == @selector(sayNB)) {  
        IMP imp           = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));  
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));  
        const char *type  = method_getTypeEncoding(sayMMethod);  
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);  
    }  
    return NO;  
}
/**
 1: 分类 - 便利
 2: 方法 - lg_model_tracffic
        - lg - model home - 奔溃 - pop Home
        - lg - mine  - mine
    切面 - SDK - 上传
 3: AOP - 封装SDK - 不处理
 4: 消息转发 - 
 */


第二次你可以在forwardingTargetForSelector中拦截, 也就是快速转发


- (id)forwardingTargetForSelector:(SEL)aSelector{  
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));  
    if(aSelector == @selector(sayBad)){  
        return [LGStudent sayHello];  
    }  
    // runtime + aSelector + addMethod + imp  
    return [super forwardingTargetForSelector:aSelector];  
}


第三次你可以在methodSignatureForSelector进行拦截, 也就是慢速转发


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{  
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));  
    return nil;  
}  
- (void)forwardInvocation:(NSInvocation *)anInvocation{  
    NSLog(@"%s - %@",__func__,anInvocation);  
    // GM  sayHello - anInvocation - 漂流瓶 - anInvocation  
    anInvocation.target = [LGStudent alloc];  
    // anInvocation 保存 - 方法  
    [anInvocation invoke];  
}


最终我们可以总结成一幅图, 也就是网上比较常见的一幅图:


微信图片_20220509110433.jpg

消息转发机制


本篇文章可以用一幅图来总结一下, 与上面对比


微信图片_20220509110442.jpg

文章总结


3. 有关反汇编


在方法崩溃后, 我们可以通过指令bt来打印堆栈截图如下:


微信图片_20220509110446.jpg

堆栈截图


我们看到在执行doesNotRecognizedSelector之前,执行__forwarding_prep_0______forwarding___

而在__forwarding_prep_0___以及___forwarding___输入CoreFoundation框架的方法, 并且不对外开源, 我们可以找到 CoreFoundation的可运行文件, 用反编译工具来进行反编辑就可以查看反编译猴的汇编代码来分析流程, 可以分析出跟上面我们分析的流程一下, 这里只做记录:


微信图片_20220509110451.jpg

找到CoreFoundation可运行文件路径


反编译结果(用的Hopper Disassembler):


微信图片_20220509110455.jpg

反编译结果:




目录
打赏
0
0
0
0
5
分享
相关文章
【力扣刷题】只出现一次的数字、多数元素、环形链表 II、两数相加
【力扣刷题】只出现一次的数字、多数元素、环形链表 II、两数相加
80 1
C语言:练习6
C语言:练习6
54 1
数据库设计方法论 - 继承
数据库设计方法论 - 继承
223 0
你是真的“C”——宏与函数的英雄本色
一、何为宏? 在进行宏和函数的对比时,我们先了解一下什么是宏。 #define 定义宏 #define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。 下面是宏的申明方式: #define name( parament-list ) stuff 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中
160 0
SpringCloud - Hystrix(三)
SpringCloud - Hystrix(三)
123 0
SpringCloud - Hystrix(三)
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等