iOS 底层分析

Runtime 二:msg_msgSend执行流程

2019-12-02  本文已影响0人  小心韩国人

Runtime 一: OC 方法的底层数据结构和存储机制我们知道了OC类的底层结构,类方法的存储位置以及方法缓存的策略.今天我们搞清楚OC方法调用的本质:消息发送机制 - msg_Send.
我们随便写一个方法[person test];,转成C++代码,发现它的底层是这样的:

[person test] 方法底层
OC方法调用的本质就是给对象发送消息:objc_msgSend(),这个流程可以分为三个阶段:

一:消息发送代码分析:

查找源码步骤:
打开runtime源码 -> 搜索objc_msgSend -> 找到objc-msg-arm64.s文件 -> 找到ENTRY _objc_msgSend(方法入口):

objc_msgSend 方法内部
我们用伪代码写一下上图的逻辑:
objc_msgSend(person , sel){
    if (person == nil)  return;
    // 如果不为 nil ,从缓存中查找...
}

从上图中可以看到,如果reserver不为nil,就执行CacheLookup从缓存中查找,所以我们搜索CacheLookup方法:

CacheLookup 方法
搜索CheckMiss:
CheckMiss
搜索__objc_msgLookup_uncached:
__objc_msgLookup_uncached
搜索MethodTableLookup:
MethodTableLookup
搜索__class_lookupMethodAndLoadCache3,咦~一搜索发现这个方法就这一处存在,搜索不到了.既然汇编里面没有了,那会不会是用C语言实现的这个方法呢?我们去掉一个下划线_然后搜索(为什么要去掉一个下划线呢?因为C语言在编译成汇编语言是,会默认在方法前面加一个下划线_,所以我们在C语言中搜索时要去掉一个_),发现还真搜索到了这个方法:
_class_lookupMethodAndLoadCache3
进入lookUpImpOrForward:
lookUpImpOrForward
从上图中可以很清楚的看到看到方法的查找过程,注释把每一步都写的很清楚了.我们继续进入getMethodNoSuper_nolock(cls, sel)方法,看看它的内部是如何从类中查找方法的:
getMethodNoSuper_nolock

进入search_method_list(mlist, sel)看看是如何查找的:

search_method_list(*mlists, sel)
ok,到现在我们就从源码层面搞清楚了objc_msgSend()第一阶段消息发送的步骤,我们用一张图总结一下:
消息发送流程

二:动态方法解析:

lookUpImpOrForward方法实现中可以看到,如果在消息发送阶段始终没有找到方法,那么就会进入动态方法解析:

动态方法解析
我们用代码验证一下.在Person.h中声明一个- (void)test方法,但是不实现这个方法.然后我们重写+ (BOOL)resolveInstanceMethod:(SEL)sel这个方法,然后运行代码:
resolveInstanceMethod
发现的确进入到resolveInstanceMethod这个方法,那我们在这个方法内部改如何操作呢?
动态添加方法实现
其实上图的Method就是我们之前讲过的method_t,我们可以用method_t替换一下上面的写法:
method_t 替换 Mehod
我们用一张图捋一下动态方法解析的步骤:
动态方法解析的步骤
关于动态方法解析的注意点:

三:消息转发:

如果在消息发送阶段没有找到方法,并且在动态方法解析阶段没有实现resolveInstanceMethod / resolveClassMethod或者在实现了这两个方法但是没有在这两个方法中添加方法,那么就会进入到第三步:消息转发阶段
进入消息转发阶段会进入- (id)forwardingTargetForSelector:(SEL)aSelector方法,这个方法返回一个id类型的对象,表示把这个消息转发给其他对象来处理,比如我们可以这样:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Person *person = [[Person alloc]init];
        //在 main 函数中调用 test 方法
        [person test];
    }
    return 0;
}

// 把 消息 转发 给 student 对象来处理
- (id)forwardingTargetForSelector:(SEL)aSelector{
    return [[Student alloc]init];
}

//最后执行的结果:
2019-12-02 15:17:02.837927+0800 msg_SendTest[9942:3757488] student 的 test 方法

forwardingTargetForSelector这个方法内部是如何处理的呢?我们还是从源码上找答案:

进入消息转发
我们进入_objc_msgForward_impcache内部会发现没有别的线索了,只是一个声明:
_objc_msgForward_impcache
其实,进入到消息转发阶段,相关的源代码就没有开源了.所以要想研究消息转发阶段到底做了什么,只能通过逆向了分析,可惜我又不会逆向,😆,好尴尬😳.辛亏有国外的大神把消息转发的流程通过伪代码写了出来:
int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);

    // 调用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
            NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

            [receiver forwardInvocation:invocation];

            void *returnValue = NULL;
            [invocation getReturnValue:&value];
            return returnValue;
        }
    }

    if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }

    // The point of no return.
    kill(getpid(), 9);
}

为什么是__forwarding__这个方法呢?我们把forwardingTargetForSelector方法注释掉,看看系统会报什么错误:

核心方法
所以进入消息转发阶段后,就会进入__forwarding__方法处理.我么重点研究这个方法.上面已经把伪代码的核心部分提炼出来了,下面我通过截图标注的形式说明:
方法转发处理流程
我们用代码验证一下:
验证
其实我们可以在forwardInvocation中做任何事情,因为NSInvocation内部有我们想要的任何东西:
NSInvocation 内部
为所欲为:
forwardInvocation 为所欲为

以上都是以实力方法为例,下面我们把-(void)test;更改为+(void)test:
更改为类方法后一运行报错,这是为什么呢?我们仔细看看伪代码:

调用细节
所以,如果是类方法,我们就要这样写:
+ 方法
到这里我们就搞清楚了消息转发的所有流程和细节,我们用一张图捋一捋:
消息转发的流程

ok,终于把消息发送的全部流程写完了,说实话,写文章挺费时间的,加油坚持吧.

上一篇下一篇

猜你喜欢

热点阅读