iOS Runtime學(xué)習(xí)(二) -- Runtime執(zhí)行順序

一、什么是Runtime?

OC是一門動(dòng)態(tài)類型的語言,它允許很多操作都推遲到程序運(yùn)行時(shí)再進(jìn)行
OC的動(dòng)態(tài)性就是由Runtime來支撐和實(shí)現(xiàn)的,而Runtime是一套C語言的API,它封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)
我們平時(shí)編寫的OC代碼,其實(shí)底層都是將代碼轉(zhuǎn)換成了Runtime API來進(jìn)行調(diào)用

二、OC的消息機(jī)制

OC的方法調(diào)用其實(shí)都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用,給方法的調(diào)用者發(fā)送了一條消息。
objc_msgSend底層有3個(gè)階段

  • 消息發(fā)送(當(dāng)前類、父類中查找)
  • 動(dòng)態(tài)方法解析
  • 消息轉(zhuǎn)發(fā)

三、流程解析

例如,我們有一個(gè)Person類,將這個(gè)Person實(shí)例化對(duì)象
先導(dǎo)入頭文件

#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));

新工程這里會(huì)報(bào)錯(cuò)Too many arguments to function cal
這個(gè)問題只需要在FuDemo->Target中Build Setting的Enable Strict Checking of objc_msgSend Calls的值設(shè)置為NO即可。

運(yùn)行程序,我們能看到,person1與person2都創(chuàng)建成功了,接下來就看看匯編代碼片段是不是執(zhí)行了objc_msgSend方法。首先將Person1的初始化代碼注釋掉,然后打開Always Show Disassembly,讓我們?cè)谡{(diào)試時(shí),斷點(diǎn)能直接進(jìn)入到匯編代碼界面,如下圖:

1.png
最后將斷點(diǎn)打在Person初始化那一行,Command+R。
從匯編代碼界面,我們能看到如下信息:
Person進(jìn)行了alloc,然后該對(duì)象調(diào)用了objc_msgSend進(jìn)行init。
將斷點(diǎn)執(zhí)行到調(diào)用objc_msgSend方法,‘按住Control + step into’查看objc_msgSend內(nèi)部實(shí)現(xiàn),再用同樣方法查看objc_msgSend_uncached,最終我們可以找到class_lookupMethodAndLoadCache3這樣的調(diào)用步驟。

四、objc_msgSend執(zhí)行流程

工程搜索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
 *
 ********************************************************************/

這下面就是實(shí)現(xiàn)的主要步驟,我們抽取主要信息查看

    /*進(jìn)入到objc_msgSend*/
    ENTRY _objc_msgSend             
    
    /*查找當(dāng)前isa*/
    GetIsaFast NORMAL       // r10 = self->isa
    /*從緩存中查找當(dāng)前方法,如果查找到了IMP將結(jié)果返回給調(diào)用者*/
    CacheLookup NORMAL, CALL    // calls IMP on success
/*在緩存中沒找到,搜索方法列表*/
// cache miss: go search the method lists
LCacheMiss:
    /*\*/
    // isa still in r10
    /*跳轉(zhuǎn)objc_msgSend_uncached*/
    jmp __objc_msgSend_uncached

objc_msgSend_uncached內(nèi)基本都是在調(diào)用MethodTableLookup(從當(dāng)前方法列表中查找)。如果找到,則將IMP放到寄存器中。
MethodTableLookup能找到class_lookupMethodAndLoadCache3被調(diào)用,正好這也是之前我們所驗(yàn)證的最后一部。
class_lookupMethodAndLoadCache3中只做了lookUpImpOrForward(查找方法實(shí)現(xiàn))這一件事
因此runtime的執(zhí)行順序?yàn)椋?/p>

1.objc_msgSend
2.CacheLookup(有緩存IMP,則返回給調(diào)用者。沒有緩存則往下執(zhí)行)
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;
    }

    /*確保當(dāng)前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();

    /*從當(dāng)前類的緩存中查找*/
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    /*從當(dāng)前類的方法列表中查找*/
    {
        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,嘗試動(dòng)態(tài)決議 resolveInstanceMethod*/
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        triedResolver = YES;
        goto retry;
    }

    /*沒有實(shí)現(xiàn)動(dòng)態(tài)決議方法,觸發(fā)消息轉(zhuǎn)發(fā)流程 (forwardingTargetForSelector和forwardInvocation)*/
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,076評(píng)論 0 9
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,675評(píng)論 1 32
  • 前言 runtime其實(shí)在我們?nèi)粘i_發(fā)過程中很少使用到,尤其是像我現(xiàn)在比較初級(jí)的程序猿就更用不到了。但是去面試很多...
    MurtoTien閱讀 857評(píng)論 0 2
  • 他們都想去島上碰碰運(yùn)氣,湯尼和九只小蝙蜥留在了咕嚕船上。 安德滿在海灘上摸清了沙子的溫度是正常的。 他們走過了白色...
    天修極樂閱讀 519評(píng)論 0 0
  • 我有一份清閑的工作,閑暇時(shí)間幫著男友打理生意,開一個(gè)小店就像一個(gè)小小的創(chuàng)業(yè),需要資金運(yùn)轉(zhuǎn),需要有效的盈利模式,需要...
    Stone_blossom閱讀 449評(píng)論 0 1

友情鏈接更多精彩內(nèi)容