iOS runtime整理

runtime簡(jiǎn)介

Runtime 又叫運(yùn)行時(shí),是一套底層的 C 語(yǔ)言 API,是 iOS 系統(tǒng)的核心之一。開發(fā)者在編碼過(guò)程中,可以給任意一個(gè)對(duì)象發(fā)送消息,在編譯階段只是確定了要向接收者發(fā)送這條消息,而接受者將要如何響應(yīng)和處理這條消息,那就要看運(yùn)行時(shí)來(lái)決定了。 C語(yǔ)言中,在編譯期,函數(shù)的調(diào)用就會(huì)決定調(diào)用哪個(gè)函數(shù)。 而OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過(guò)程,在編譯期并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行時(shí)才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。

typedef struct objc_class *Class;
typedef struct objc_object *id;

@interface Object { 
    Class isa; 
}

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}
  • Objective-C 對(duì)象都是 C 語(yǔ)言結(jié)構(gòu)體實(shí)現(xiàn)的,所有的對(duì)象都會(huì)包含一個(gè)isa_t類型的結(jié)構(gòu)體
  • objc_class繼承于objc_object。所以在objc_class中也會(huì)包含isa_t類型的結(jié)構(gòu)體isa。至此,可以得出結(jié)論:Objective-C 中類也是一個(gè)對(duì)象。
  • 我們可以認(rèn)為id中的isa指針指向的是一個(gè)類對(duì)象,并且在Class結(jié)構(gòu)體中的isa指針指向元類

對(duì)象的實(shí)例方法調(diào)用時(shí),通過(guò)對(duì)象的 isa 在類中獲取方法的實(shí)現(xiàn)。
類對(duì)象的類方法調(diào)用時(shí),通過(guò)類的 isa 在元類中獲取方法的實(shí)現(xiàn)。


對(duì)象,類,元類之間的關(guān)系

SEL

typedef struct objc_selector *SEL;

objc_selector是一個(gè)映射到方法的C字符串。SEL是系統(tǒng)在編譯過(guò)程中,會(huì)根據(jù)方法的名字以及參數(shù)序列生成一個(gè)用來(lái)區(qū)分這個(gè)方法的唯一ID編號(hào),這個(gè) ID 就是 SEL 類型的。我們需要注意的是,不同類中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器。由于這點(diǎn)特性,也導(dǎo)致了OC不支持函數(shù)重載。
獲取SEL的幾種方法:

SEL aSel = @selector(didReceiveMemoryWarning);
SEL a_sel = NSSelectorFromString(@"didReceiveMemoryWarning");
SEL a_Sel = sel_registerName("didReceiveMemoryWarning");
NSLog(@"%p___%p___%p",aSel,a_sel,a_Sel);

Method

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;  //方法名
    char *method_types                                       OBJC2_UNAVAILABLE;  //參數(shù)類型以及返回值類型編碼
    IMP method_imp                                           OBJC2_UNAVAILABLE; //方法實(shí)現(xiàn)指針
}

獲取方法

// 獲取實(shí)例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

IMP

IMP即Implementation,是一個(gè)函數(shù)指針,指向的是函數(shù)的具體實(shí)現(xiàn)。在runtime中消息傳遞和轉(zhuǎn)發(fā)的目的就是為了找到IMP,并執(zhí)行函數(shù)。
獲取IMP的方法:

//通過(guò)Method獲取IMP
IMP method_getImplementation(Method m);
// 返回方法的具體實(shí)現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

runtime消息發(fā)送與轉(zhuǎn)發(fā)

消息發(fā)送和轉(zhuǎn)發(fā)
  • NilTest是用來(lái)檢測(cè)是否為nil的,如果檢測(cè)方法的接受者是nil,那么系統(tǒng)會(huì)自動(dòng)clean并且return。
  • Cache的作用主要是為了優(yōu)化方法調(diào)用的性能。當(dāng)對(duì)象receiver調(diào)用方法message時(shí),首先根據(jù)對(duì)象receiver的isa指針查找到它對(duì)應(yīng)的類,然后在類的methodLists中搜索方法,如果沒(méi)有找到,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法。如果沒(méi)有找到,有可能消息轉(zhuǎn)發(fā),也可能忽略它。但這樣查找方式效率太低,因?yàn)橥粋€(gè)類大概只有20%的方法經(jīng)常被調(diào)用,占總調(diào)用次數(shù)的80%。所以使用Cache來(lái)緩存經(jīng)常調(diào)用的方法,當(dāng)調(diào)用方法時(shí),優(yōu)先在Cache查找,如果沒(méi)有找到,再到methodLists查找。
  • MethodTableLookup 可以算是個(gè)接口層宏,主要用于保存環(huán)境與準(zhǔn)備參數(shù),來(lái)調(diào)用 __class_lookupMethodAndLoadCache3函數(shù)
  • __class_lookupMethodAndLoadCache3函數(shù)也是個(gè)接口層(C編寫),此函數(shù)提供相應(yīng)參數(shù)配置,實(shí)際功能在lookUpImpOrForward函數(shù)中。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    if (!cls->isRealized()) {
        realizeClass(cls);
    }
    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
    }

 retry:    
    // Try this class's cache.
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Stop searching, but don't cache yet; call method  resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        _class_resolveMethod(cls, sel, inst);
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help.  Use forwarding.
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

    return imp;
}
  • 調(diào)用realizeClass方法是申請(qǐng)class_rw_t的可讀寫空間。
  • _class_initialize是類初始化的過(guò)程。
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}
  • 這個(gè)函數(shù)首先判斷是否是meta-class類,如果不是元類,就執(zhí)行_class_resolveInstanceMethod,如果是元類,執(zhí)行_class_resolveClassMethod。
  • 為什么查找類方法的實(shí)現(xiàn)過(guò)程中會(huì)查看resolveInstanceMethod的實(shí)現(xiàn)。
    其根本原因?yàn)?NSObject 為root meta class類的根類,而root meta class 為所有其他meta class的 ISA指向的類。在類方法的巡查過(guò)程中,通過(guò)meta class的繼承關(guān)系會(huì)最終找到NSObject類,所以在NSObject 的resolveInstanceMethod添加方法決議實(shí)現(xiàn)代碼,并且在類方法(+號(hào)方法)的決議過(guò)程中檢測(cè)該實(shí)現(xiàn)是合理的。
消息轉(zhuǎn)發(fā)

動(dòng)態(tài)方法解析
+resolveInstanceMethod:(實(shí)例方法)或者+resolveClassMethod:(類方法);我們有機(jī)會(huì)為該未知消息新增一個(gè)”處理方法””,不過(guò)使用該方法的前提是我們已經(jīng)實(shí)現(xiàn)了該”處理方法”

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"methodO:"]) {
        Method addM = class_getInstanceMethod([self class], sel_registerName("functionMethodAddO:"));
        class_addMethod([self class], sel, method_getImplementation(addM), method_getTypeEncoding(addM));
    }
    return [super resolveInstanceMethod:sel];
}

備用接收者
如果一個(gè)對(duì)象實(shí)現(xiàn)了這個(gè)方法,并返回一個(gè)非nil的結(jié)果,則這個(gè)對(duì)象會(huì)作為消息的新接收者,且消息會(huì)被分發(fā)到這個(gè)對(duì)象

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *selectorString = NSStringFromSelector(aSelector);
    Class cls = NSClassFromString(@"MrHelp");
    // 將消息轉(zhuǎn)發(fā)給MrHelp類來(lái)處理
    if ([selectorString isEqualToString:@"methodTw"]) {
        return [cls new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

完整消息轉(zhuǎn)發(fā)
運(yùn)行時(shí)系統(tǒng)會(huì)在這一步給消息接收者最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對(duì)象。對(duì)象會(huì)創(chuàng)建一個(gè)表示消息的NSInvocation對(duì)象,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中,包括selector,目標(biāo)(target)和參數(shù)。在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能,我們可以對(duì)消息的內(nèi)容進(jìn)行修改,比如追回一個(gè)參數(shù)等,然后再去觸發(fā)消息

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    Class cls = NSClassFromString(@"MrHelp");
    NSMethodSignature *singature= [super methodSignatureForSelector:aSelector];
    if (!singature) {
        singature = [cls instanceMethodSignatureForSelector:sel_registerName("universalMethod:AndClass:")];
    }
    return singature;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    NSString *selectorString = NSStringFromSelector(anInvocation.selector);
    Class cls = NSClassFromString(@"MrHelp");
    id methodStr =selectorString;
    id className = [anInvocation.target class];
    [anInvocation setArgument:&methodStr atIndex:2];//第一個(gè)參數(shù)
    [anInvocation setArgument:&className atIndex:3];//第而個(gè)參數(shù)
    
    [anInvocation setSelector:sel_registerName("universalMethod:AndClass:")];//universalMethod:AndClass:AndArg:
    [anInvocation invokeWithTarget:[cls new]];
}
最后編輯于
?著作權(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),簡(jiǎ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,030評(píng)論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,872評(píng)論 33 466
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂(lè)樂(lè)的簡(jiǎn)書閱讀 2,240評(píng)論 0 9
  • 我們常常會(huì)聽說(shuō) Objective-C 是一門動(dòng)態(tài)語(yǔ)言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,319評(píng)論 0 7
  • 目錄 Objective-C Runtime到底是什么 Objective-C的元素認(rèn)知 Runtime詳解 應(yīng)用...
    Ryan___閱讀 2,008評(píng)論 1 3

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