美文网首页
iOS Runtime学习(二) -- Runtime执行顺序

iOS Runtime学习(二) -- Runtime执行顺序

作者: Q海龙 | 来源:发表于2019-03-15 17:12 被阅读0次

    一、什么是Runtime?

    OC是一门动态类型的语言,它允许很多操作都推迟到程序运行时再进行
    OC的动态性就是由Runtime来支撑和实现的,而Runtime是一套C语言的API,它封装了很多动态性相关的函数
    我们平时编写的OC代码,其实底层都是将代码转换成了Runtime API来进行调用

    二、OC的消息机制

    OC的方法调用其实都是转成了objc_msgSend函数的调用,给方法的调用者发送了一条消息。
    objc_msgSend底层有3个阶段

    • 消息发送(当前类、父类中查找)
    • 动态方法解析
    • 消息转发

    三、流程解析

    例如,我们有一个Person类,将这个Person实例化对象
    先导入头文件

    #import <objc/runtime.h>
    #import <objc/message.h>
    #import "Person.h"
    
    //oc写法
    Person *person1 = [[Person alloc] init];
    //runtime写法
    Person *person2 = objc_msgSend(objc_msgSend([Person class], @selector(alloc)), @selector(init));
    

    新工程这里会报错Too many arguments to function cal
    这个问题只需要在FuDemo->Target中Build Setting的Enable Strict Checking of objc_msgSend Calls的值设置为NO即可。

    运行程序,我们能看到,person1与person2都创建成功了,接下来就看看汇编代码片段是不是执行了objc_msgSend方法。首先将Person1的初始化代码注释掉,然后打开Always Show Disassembly,让我们在调试时,断点能直接进入到汇编代码界面,如下图:

    1.png 最后将断点打在Person初始化那一行,Command+R。
    从汇编代码界面,我们能看到如下信息:
    Person进行了alloc,然后该对象调用了objc_msgSend进行init
    将断点执行到调用objc_msgSend方法,‘按住Control + step into’查看objc_msgSend内部实现,再用同样方法查看objc_msgSend_uncached,最终我们可以找到class_lookupMethodAndLoadCache3这样的调用步骤。

    四、objc_msgSend执行流程

    工程搜索objc_msgSend,找到objc-msg-x86_64.s我们可以找到如下代码片段

    /********************************************************************
     *
     * id objc_msgSend(id self, SEL _cmd,...);
     * IMP objc_msgLookup(id self, SEL _cmd, ...);
     *
     * objc_msgLookup ABI:
     * IMP returned in r11
     * Forwarding returned in Z flag
     * r10 reserved for our use but not used
     *
     ********************************************************************/
    
    

    这下面就是实现的主要步骤,我们抽取主要信息查看

        /*进入到objc_msgSend*/
        ENTRY _objc_msgSend             
        
        /*查找当前isa*/
        GetIsaFast NORMAL       // r10 = self->isa
        /*从缓存中查找当前方法,如果查找到了IMP将结果返回给调用者*/
        CacheLookup NORMAL, CALL    // calls IMP on success
    /*在缓存中没找到,搜索方法列表*/
    // cache miss: go search the method lists
    LCacheMiss:
        /*\*/
        // isa still in r10
        /*跳转objc_msgSend_uncached*/
        jmp __objc_msgSend_uncached
    

    objc_msgSend_uncached内基本都是在调用MethodTableLookup(从当前方法列表中查找)。如果找到,则将IMP放到寄存器中。
    MethodTableLookup能找到class_lookupMethodAndLoadCache3被调用,正好这也是之前我们所验证的最后一部。
    class_lookupMethodAndLoadCache3中只做了lookUpImpOrForward(查找方法实现)这一件事
    因此runtime的执行顺序为:

    1.objc_msgSend
    2.CacheLookup(有缓存IMP,则返回给调用者。没有缓存则往下执行)
    3.objc_msgSend_unCached
    4.MethodTableLookup
    5.class_lookupMethodAndLoadCache3
    6.lookUpImpOrForward
    

    五、lookUpImpOrForward代码片段注释

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        /*从缓存中查找*/
        if (cache) {
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
    
        /*确保当前isa初始化*/
        checkIsKnownClass(cls);
        if (!cls->isRealized()) {
            realizeClass(cls);
        }
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlock();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.lock();
        }
     
     retry:    
        runtimeLock.assertLocked();
    
        /*从当前类的缓存中查找*/
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    
        /*从当前类的方法列表中查找*/
        {
            Method meth = getMethodNoSuper_nolock(cls, sel);
            if (meth) {
                /*将查找到的meth放入缓存中*/
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
    
        /*沿着继承链向上查找*/
        {
            unsigned attempts = unreasonableClassCount();
            for (Class curClass = cls->superclass;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                if (--attempts == 0) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        break;
                    }
                }
                
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                    imp = meth->imp;
                    goto done;
                }
            }
        }
    
        /*没有找到IMP,尝试动态决议 resolveInstanceMethod*/
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlock();
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.lock();
            triedResolver = YES;
            goto retry;
        }
    
        /*没有实现动态决议方法,触发消息转发流程 (forwardingTargetForSelector和forwardInvocation)*/
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlock();
    
        return imp;
    }
    

    相关文章

      网友评论

          本文标题:iOS Runtime学习(二) -- Runtime执行顺序

          本文链接:https://www.haomeiwen.com/subject/wofcmqtx.html