Runtime-iOS運行時基礎(chǔ)篇

本文主要整理了Runtime的相關(guān)知識离熏。對于一個iOS開發(fā)者來說履婉,掌握Runtime的重要性早已不言而喻闲擦。OC能夠作為一門優(yōu)秀的動態(tài)特性語言萌壳,在其背后默默工作著的就是Runtime。在網(wǎng)上也看過很多資料摹芙,最終我還是希望在一些關(guān)鍵的知識點上能夠融入自己的理解灼狰,從簡單的問題出發(fā),一步一步理解和學(xué)以致用浮禾。

iOS運行時Runtime.png

相關(guān)文章:iOS運行時Runtime應(yīng)用

目錄:

一交胚、怎么理解OC是動態(tài)語言,Runtime又是什么盈电?
二蝴簇、理解消息機制的基本原理
三、與Runtime交互的三種方式
四挣轨、分析Runtime中的數(shù)據(jù)結(jié)構(gòu)
五军熏、深入理解Rutime消息發(fā)送原理
六、多繼承的實現(xiàn)思路:Runtime
七卷扮、最后總結(jié)

一荡澎、怎么理解OC是動態(tài)語言,Runtime又是什么晤锹?

靜態(tài)語言:如C語言摩幔,編譯階段就要決定調(diào)用哪個函數(shù),如果函數(shù)未實現(xiàn)就會編譯報錯鞭铆。

動態(tài)語言:如OC語言或衡,編譯階段并不能決定真正調(diào)用哪個函數(shù),只要函數(shù)聲明過即使沒有實現(xiàn)也不會報錯车遂。

我們常說OC是一門動態(tài)語言封断,就是因為它總是把一些決定性的工作從編譯階段推遲到運行時階段。OC代碼的運行不僅需要編譯器舶担,還需要運行時系統(tǒng)(Runtime Sytem)來執(zhí)行編譯后的代碼坡疼。

Runtime是一套底層純C語言API,OC代碼最終都會被編譯器轉(zhuǎn)化為運行時代碼衣陶,通過消息機制決定函數(shù)調(diào)用方式柄瑰,這也是OC作為動態(tài)語言使用的基礎(chǔ)。

二剪况、理解消息機制的基本原理

OC的方法調(diào)用都是類似[receiver selector]的形式教沾,其實每次都是一個運行時消息發(fā)送過程。

第一步:編譯階段
[receiver selector]方法被編譯器轉(zhuǎn)化译断,分為兩種情況:
1.不帶參數(shù)的方法被編譯為:objc_msgSend(receiver授翻,selector)
2.帶參數(shù)的方法被編譯為:objc_msgSend(recevier,selector,org1堪唐,org2隆箩,…)

第二步:運行時階段
消息接收者recever尋找對應(yīng)的selector,也分為兩種情況:
1.接收者能找到對應(yīng)的selector羔杨,直接執(zhí)行接收receiver對象的selector方法。
2.接收者找不到對應(yīng)的selector杨蛋,消息被轉(zhuǎn)發(fā)或者臨時向接收者添加這個selector對應(yīng)的實現(xiàn)內(nèi)容兜材,否則崩潰。

說明:OC調(diào)用方法[receiver selector]逞力,編譯階段確定了要向哪個接收者發(fā)送message消息曙寡,但是接收者如何響應(yīng)決定于運行時的判斷。

三寇荧、與Runtime的交互

Runtime的官方文檔中將OC與Runtime的交互劃分三種層次:OC源代碼举庶,NSObject方法Runtime 函數(shù)揩抡。這其實也是按照與Runtime交互程度從低到高排序的三種方式户侥。

1.OC源代碼(Objec-C Source Code)

我們已經(jīng)說過,OC代碼會在編譯階段被編譯器轉(zhuǎn)化峦嗤。OC中的類蕊唐、方法和協(xié)議等在Runtime中都由一些數(shù)據(jù)結(jié)構(gòu)來定義。所以烁设,我們平時直接使用OC編寫代碼替梨,其實這已經(jīng)是在和Runtime進(jìn)行交互了,只不過這個過程對于我們來說是無感的装黑。

2.NSObject方法(NSObject Methods)

Runtime的最大特征就是實現(xiàn)了OC語言的動態(tài)特性副瀑。作為大部分Objective-C類繼承體系的根類的NSObject,其本身就具有了一些非常具有運行時動態(tài)特性的方法恋谭,比如respondsToSelector:方法可以檢查在代碼運行階段當(dāng)前對象是否能響應(yīng)指定的消息糠睡,所以使用這些方法也算是一種與Runtme的交互方式,類似的方法還有如下:

-description://返回當(dāng)前類的描述信息

-class //方法返回對象的類箕别;

-isKindOfClass: 和 -isMemberOfClass:  //檢查對象是否存在于指定的類的繼承體系中

-respondsToSelector:    //檢查對象能否響應(yīng)指定的消息铜幽;

-conformsToProtocol:    //檢查對象是否實現(xiàn)了指定協(xié)議類的方法;

-methodForSelector:     //返回指定方法實現(xiàn)的地址串稀。

3.使用Runtime函數(shù)(Runtime Functions)

Runtime系統(tǒng)是一個由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成除抛,具有公共接口的動態(tài)共享庫。頭文件存放于/usr/include/objc目錄下母截。在我們工程代碼里引用Runtime的頭文件到忽,同樣能夠?qū)崿F(xiàn)類似OC代碼的效果,一些代碼示例如下:

//相當(dāng)于:Class class = [UIView class];
Class viewClass = objc_getClass("UIView");
    
//相當(dāng)于:UIView *view = [UIView alloc];
UIView *view = ((id (*)(id, SEL))(void *)objc_msgSend)((id)viewClass, sel_registerName("alloc"));

//相當(dāng)于:UIView *view = [view init];
((id (*)(id, SEL))(void *)objc_msgSend)((id)view, sel_registerName("init"));

三、分析Runtime中數(shù)據(jù)結(jié)構(gòu)

OC代碼被編譯器轉(zhuǎn)化為C語言喘漏,然后再通過運行時執(zhí)行护蝶,最終實現(xiàn)了動態(tài)調(diào)用。這其中的OC類翩迈、對象和方法等都對應(yīng)了C中的結(jié)構(gòu)體持灰,而且我們都可以在Rutime源碼中找到它們的定義。

那么负饲,我們?nèi)绾蝸聿榭碦untime的代碼呢堤魁?其實很簡單,只需要我們在當(dāng)前代碼文件中引用頭文件:

#import <objc/runtime.h>
#import <objc/message.h>

然后返十,我們需要使用組合鍵"Command +鼠標(biāo)點擊"妥泉,即可進(jìn)入Runtime的源碼文件,下面我們繼續(xù)來一一分析OC代碼在C中對應(yīng)的結(jié)構(gòu)洞坑。

1.id—>objc_object

id是一個指向objc_object結(jié)構(gòu)體的指針盲链,即在Runtime中:

///A pointer to an instance of a class.
typedef struct objc_object *id;

下面是Runtime中對objc_object結(jié)構(gòu)體的具體定義:

///Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

我們都知道id在OC中是表示一個任意類型的類實例,從這里也可以看出迟杂,OC中的對象雖然沒有明顯的使用指針刽沾,但是在OC代碼被編譯轉(zhuǎn)化為C之后,每個OC對象其實都是擁有一個isa的指針的排拷。

2.Class - >objc_classs

class是一個指向objc_class結(jié)構(gòu)體的指針悠轩,即在Runtime中:

typedef struct objc_class *Class; 

下面是Runtime中對objc_class結(jié)構(gòu)體的具體定義:

//usr/include/objc/runtime.h
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
#if !OBJC2

    Class Nullable super_class                              OBJC2UNAVAILABLE;
    const char * Nonnull name                               OBJC2UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * Nullable ivars                  OBJC2UNAVAILABLE;
    struct objc_method_list * Nullable * _Nullable methodLists                    OBJC2UNAVAILABLE;
    struct objc_cache * Nonnull cache                       OBJC2UNAVAILABLE;
    struct objc_protocol_list * Nullable protocols          OBJC2UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

理解objc_class定義中的參數(shù):

isa指針:

我們會發(fā)現(xiàn)objc_class和objc_object同樣是結(jié)構(gòu)體,而且都擁有一個isa指針攻泼。我們很容易理解objc_object的isa指針指向?qū)ο蟮亩x火架,那么objc_class的指針是怎么回事呢?
其實忙菠,在Runtime中Objc類本身同時也是一個對象何鸡。Runtime把類對象所屬類型就叫做元類,用于描述類對象本身所具有的特征牛欢,最常見的類方法就被定義于此骡男,所以objc_class中的isa指針指向的是元類,每個類僅有一個類對象傍睹,而每個類對象僅有一個與之相關(guān)的元類隔盛。

super_class指針:

super_class指針指向objc_class類所繼承的父類,但是如果當(dāng)前類已經(jīng)是最頂層的類(如NSProxy),則super_class指針為NULL

cache:

為了優(yōu)化性能拾稳,objc_class中的cache結(jié)構(gòu)體用于記錄每次使用類或者實例對象調(diào)用的方法吮炕。這樣每次響應(yīng)消息的時候,Runtime系統(tǒng)會優(yōu)先在cache中尋找響應(yīng)方法访得,相比直接在類的方法列表中遍歷查找龙亲,效率更高陕凹。

ivars:

ivars用于存放所有的成員變量和屬性信息,屬性的存取方法都存放在methodLists中鳄炉。

methodLists:

methodLists用于存放對象的所有成員方法杜耙。

3.SEL

SEL是一個指向objc_selector結(jié)構(gòu)體的指針,即在Runtime中:

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

SEL在OC中稱作方法選擇器拂盯,用于表示運行時方法的名字佑女,然而我們并不能在Runtime中找到它的結(jié)構(gòu)體的詳細(xì)定義。Objective-C在編譯時谈竿,會依據(jù)每一個方法的名字珊豹、參數(shù)序列,生成一個唯一的整型標(biāo)識(Int類型的地址)榕订,這個標(biāo)識就是SEL。

注意
1.不同類中相同名字的方法對應(yīng)的方法選擇器是相同的蜕便。
2.即使是同一個類中劫恒,方法名相同而變量類型不同也會導(dǎo)致它們具有相同的方法選擇器。

通常我們獲取SEL有三種方法:
1.OC中轿腺,使用@selector(“方法名字符串”)
2.OC中两嘴,使用NSSelectorFromString(“方法名字符串”)
3.Runtime方法,使用sel_registerName(“方法名字符串”)

4.Ivar

Ivar代表類中實例變量的類型族壳,是一個指向ojbcet_ivar的結(jié)構(gòu)體的指針憔辫,即在Runtime中:

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

下面是Runtime中對objc_ivar結(jié)構(gòu)體的具體定義:

struct objc_ivar {
    char * Nullable ivar_name                               OBJC2UNAVAILABLE;
    char * Nullable ivar_type                               OBJC2UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef LP64
    int space                                                OBJC2_UNAVAILABLE;
#endif
}          

我們在objc_class中看到的ivars成員列表,其中的元素就是Ivar,我可以通過實例查找其在類中的名字仿荆,這個過程被稱為反射贰您,下面的class_copyIvarList獲取的不僅有實例變量還有屬性:

   Ivar *ivarList = class_copyIvarList([self class], &count);
    for (int i= 0; i<count; i++) {
        Ivar ivar = ivarList[i];
        const char *ivarName = ivar_getName(ivar);
        NSLog(@"Ivar(%d): %@", i, [NSString stringWithUTF8String:ivarName]);
    }
    free(ivarList);

5.Method

Method表示某個方法的類型拢操,即在Runtime中:

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

我們可以在objct_class定義中看到methodLists锦亦,其中的元素就是Method,下面是Runtime中objc_method結(jié)構(gòu)體的具體定義:

struct objc_method {
    SEL Nonnull method_name                                 OBJC2UNAVAILABLE;
    char * Nullable method_types                            OBJC2UNAVAILABLE;
    IMP Nonnull method_imp                                  OBJC2UNAVAILABLE;
}                                                           OBJC2_UNAVAILABLE;

理解objc_method定義中的參數(shù):
method_name:方法名類型SEL
method_types: 一個char指針令境,指向存儲方法的參數(shù)類型和返回值類型
method_imp:本質(zhì)上是一個指針杠园,指向方法的實現(xiàn)
這里其實就是SEL(method_name)與IMP(method_name)形成了一個映射,通過SEL舔庶,我們可以很方便的找到方法實現(xiàn)IMP抛蚁。

5.IMP

IMP是一個函數(shù)指針,它在Runtime中的定義如下:

/// A pointer to the function of a method implementation.
typedef void (IMP)(void / id, SEL, ... */ ); 

IMP這個函數(shù)指針指向了方法實現(xiàn)的首地址惕橙,當(dāng)OC發(fā)起消息后瞧甩,最終執(zhí)行的代碼是由IMP指針決定的。利用這個特性弥鹦,我們可以對代碼進(jìn)行優(yōu)化:當(dāng)需要大量重復(fù)調(diào)用方法的時候亲配,我們可以繞開消息綁定而直接利用IMP指針調(diào)起方法,這樣的執(zhí)行將會更加高效,相關(guān)的代碼示例如下:

void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);

注意:這里需要注意的就是函數(shù)指針的前兩個參數(shù)必須是id和SEL吼虎。

四犬钢、深入理解Rutime消息發(fā)送

我們在分析了OC語言對應(yīng)的底層C結(jié)構(gòu)之后,現(xiàn)在可以進(jìn)一步理解運行時的消息發(fā)送機制思灰。先前講到玷犹,OC調(diào)用方法被編譯轉(zhuǎn)化為如下的形式:

id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

其實,除了常見的objc_msgSend洒疚,消息發(fā)送的方法還有objc_msgSend_stret,objc_msgSendSuper,objc_msgSendSuper_stret等歹颓,如果消息傳遞給超類就使用帶有super的方法,如果返回值是結(jié)構(gòu)體而不是簡單值就使用帶有stret的值油湖。

運行時階段的消息發(fā)送的詳細(xì)步驟如下

  1. 檢測selector 是不是需要忽略的巍扛。比如 Mac OS X 開發(fā),有了垃圾回收就不理會retain,release 這些函數(shù)了乏德。
  2. 檢測target 是不是nil 對象撤奸。ObjC 的特性是允許對一個 nil對象執(zhí)行任何一個方法不會 Crash,因為會被忽略掉喊括。
  3. 如果上面兩個都過了胧瓜,那就開始查找這個類的 IMP,先從 cache 里面找郑什,若可以找得到就跳到對應(yīng)的函數(shù)去執(zhí)行府喳。
  4. 如果在cache里找不到就找一下方法列表methodLists。
  5. 如果methodLists找不到蘑拯,就到超類的方法列表里尋找钝满,一直找,直到找到NSObject類為止申窘。
  6. 如果還找不到舱沧,Runtime就提供了如下三種方法來處理:動態(tài)方法解析消息接受者重定向偶洋、消息重定向熟吏,這三種方法的調(diào)用關(guān)系如下圖:
    消息轉(zhuǎn)發(fā)流程圖.png

1.動態(tài)方法解析(Dynamic Method Resolution)

所謂動態(tài)解析,我們可以理解為通過cache和方法列表沒有找到方法時玄窝,Runtime為我們提供一次動態(tài)添加方法實現(xiàn)的機會牵寺,主要用到的方法如下:

//OC方法:
//類方法未找到時調(diào)起,可于此添加類方法實現(xiàn)
+ (BOOL)resolveClassMethod:(SEL)sel
//實例方法未找到時調(diào)起恩脂,可于此添加實例方法實現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel

//Runtime方法:
/**
 運行時方法:向指定類中添加特定方法實現(xiàn)的操作
 @param cls 被添加方法的類
 @param name selector方法名
 @param imp 指向?qū)崿F(xiàn)方法的函數(shù)指針
 @param types imp函數(shù)實現(xiàn)的返回值與參數(shù)類型
 @return 添加方法是否成功
 */
BOOL class_addMethod(Class _Nullable cls,
                     SEL _Nonnull name,
                     IMP _Nonnull imp,
                     const char * _Nullable types)

下面使用一個示例來說明動態(tài)解析:Perosn類中聲明方法卻未添加實現(xiàn)帽氓,我們通過Runtime動態(tài)方法解析的操作為其他添加方法實現(xiàn),具體代碼如下:

//Person.h文件

@interface Person : NSObject
//聲明類方法俩块,但未實現(xiàn)
+ (void)haveMeal:(NSString *)food;
//聲明實例方法黎休,但未實現(xiàn)
- (void)singSong:(NSString *)name;
@end
//Person.m文件

#import "Person.h"
#import <objc/runtime.h>
@implementation Person
//重寫父類方法:處理類方法
+ (BOOL)resolveClassMethod:(SEL)sel{
    if(sel == @selector(haveMeal:)){
        class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(zs_haveMeal:)), "v@");
        return YES;   //添加函數(shù)實現(xiàn)浓领,返回YES
    }
    return [class_getSuperclass(self) resolveClassMethod:sel];
}
//重寫父類方法:處理實例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if(sel == @selector(singSong:)){
        class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(zs_singSong:)), "v@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


+ (void)zs_haveMeal:(NSString *)food{
    NSLog(@"%s",__func__);
}

- (void)zs_singSong:(NSString *)name{
    NSLog(@"%s",__func__);
}
//TestViewController.m文件
//測試:Peson調(diào)用并未實現(xiàn)的類方法、實例方法势腮,并沒有崩潰
Person *ps = [[Person alloc] init];
[Person haveMeal:@"Apple"]; //打恿贰:+[Person zs_haveMeal:]
[ps singSong:@"紙短情長"];   //打印:-[Person zs_singSong:]

注意1:我們注意到class_addMethod方法中的特殊參數(shù)“v@”捎拯,具體可參考這里
注意2:成功使用動態(tài)方法解析還有個前提泪幌,那就是我們必須存在可以處理消息的方法,比如上述代碼中的zs_haveMeal:與zs_singSong:

2.消息接收者重定向

我們注意到動態(tài)方法解析過程中的兩個resolve方法都返回了布爾值署照,當(dāng)它們返回YES時方法即可正常執(zhí)行祸泪,但是若它們返回NO,消息發(fā)送機制就進(jìn)入了消息轉(zhuǎn)發(fā)(Forwarding)的階段了建芙,我們可以使用Runtime通過下面的方法替換消息接收者的為其他對象没隘,從而保證程序的繼續(xù)執(zhí)行。

//重定向類方法的消息接收者禁荸,返回一個類
- (id)forwardingTargetForSelector:(SEL)aSelector

//重定向?qū)嵗椒ǖ南⒔邮苷哂移眩祷匾粋€實例對象
- (id)forwardingTargetForSelector:(SEL)aSelector

下面使用一個示例來說明消息接收者的重定向:
我們創(chuàng)建一個Student類,聲明并實現(xiàn)takeExam:屡限、learnKnowledge:兩個方法,然后在視圖控制器TestViewController(一個繼承了UIViewController的自定義類)里測試炕倘,關(guān)鍵代碼如下:

//Student.h文件

@interface Student : NSObject
//類方法:參加考試
+ (void)takeExam:(NSString *)exam;
//實例方法:學(xué)習(xí)知識
- (void)learnKnowledge:(NSString *)course;
@end
//  Student.m文件

@implementation Student
+ (void)takeExam:(NSString *)exam{
    NSLog(@"%s",__func__);
}
- (void)learnKnowledge:(NSString *)course{
    NSLog(@"%s",__func__);
}
@end
//TestViewConroller.m文件
//重定向類方法:返回一個類對象
+ (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(takeExam:)) {
         
        return [Student class];
    }
    return [super forwardingTargetForSelector:aSelector];
}
//重定向?qū)嵗椒ǎ悍祷仡惖膶嵗?- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(learnKnowledge:)) {
        return self.student;
    }
    return [super forwardingTargetForSelector:aSelector];
}


//在TestViewConroller的viewDidLoad中測試:
//調(diào)用并未聲明和實現(xiàn)的類方法
[TestViewController performSelector:@selector(takeExam:) withObject:@"語文"];

//調(diào)用并未聲明和實現(xiàn)的類方法
self.student = [[Student alloc] init];
[self performSelector:@selector(learnKnowledge:) withObject:@"天文學(xué)知識"];

//正常打印:
// +[Student takeExam:]
// -[Student learnKnowledge:]

注意:動態(tài)方法解析階段返回NO時钧大,我們可以通過forwardingTargetForSelector可以修改消息的接收者,該方法返回參數(shù)是一個對象罩旋,如果這個對象是非nil啊央,非self,系統(tǒng)會將運行的消息轉(zhuǎn)發(fā)給這個對象執(zhí)行涨醋。否則瓜饥,繼續(xù)查找其他流程。

3.消息重定向

當(dāng)以上兩種方法無法生效浴骂,那么這個對象會因為找不到相應(yīng)的方法實現(xiàn)而無法響應(yīng)消息乓土,此時Runtime系統(tǒng)會通過forwardInvocation:消息通知該對象,給予此次消息發(fā)送最后一次尋找IMP的機會:

- (void)forwardInvocation:(NSInvocation *)anInvocation溯警;

其實每個對象都從NSObject類中繼承了forwardInvocation:方法趣苏,但是NSObject中的這個方法只是簡單的調(diào)用了doesNotRecongnizeSelector:方法,提示我們錯誤梯轻。所以我們可以重寫這個方法:對不能處理的消息做一些默認(rèn)處理食磕,也可以將消息轉(zhuǎn)發(fā)給其他對象來處理,而不拋出錯誤喳挑。

我們注意到anInvocation是forwardInvocation唯一參數(shù)彬伦,它封裝了原始的消息和消息參數(shù)滔悉。正是因為它,我們還不得不重寫另一個函數(shù):methodSignatureForSelector单绑。這是因為在forwardInvocation: 消息發(fā)送前回官,Runtime系統(tǒng)會向?qū)ο蟀l(fā)送methodSignatureForSelector消息,并取到返回的方法簽名用于生成NSInvocation對象询张。

下面使用一個示例來重新定義轉(zhuǎn)發(fā)邏輯:在上面的TestViewController添加如下代碼:

-(void)forwardInvocation:(NSInvocation *)anInvocation{
    //1.從anInvocation中獲取消息
    SEL sel = anInvocation.selector;
    //2.判斷Student方法是否可以響應(yīng)應(yīng)sel
    if ([self.student respondsToSelector:sel]) {
        //2.1若可以響應(yīng)孙乖,則將消息轉(zhuǎn)發(fā)給其他對象處理
        [anInvocation invokeWithTarget:self.student];
    }else{
        //2.2若仍然無法響應(yīng),則報錯:找不到響應(yīng)方法
        [self doesNotRecognizeSelector:sel];
    }
}

//需要從這個方法中獲取的信息來創(chuàng)建NSInvocation對象份氧,因此我們必須重寫這個方法唯袄,為給定的selector提供一個合適的方法簽名。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
    if (!methodSignature) {
        methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
    }
    return methodSignature;
}

然后再在視圖控制器里直接調(diào)用Student的方法如下:

//self是當(dāng)前的TestViewController,調(diào)用了自己并不存在的learnKonwledge:方法
[self performSelector:@selector(learnKnowledge:) withObject:@"天文學(xué)”];

//正常打印:
//-[Student learnKnowledge:]

總結(jié):

1.從以上的代碼中就可以看出蜗帜,forwardingTargetForSelector僅支持一個對象的返回恋拷,也就是說消息只能被轉(zhuǎn)發(fā)給一個對象,而forwardInvocation可以將消息同時轉(zhuǎn)發(fā)給任意多個對象厅缺,這就是兩者的最大區(qū)別蔬顾。

2.雖然理論上可以重載doesNotRecognizeSelector函數(shù)實現(xiàn)保證不拋出異常(不調(diào)用super實現(xiàn)),但是蘋果文檔著重提出“一定不能讓這個函數(shù)就這么結(jié)束掉湘捎,必須拋出異尘骰恚”。(If you override this method, you must call super or raise an invalidArgumentException exception at the end of your implementation. In other words, this method must not return normally; it must always result in an exception being thrown.)

3.forwardInvocation甚至能夠修改消息的內(nèi)容窥妇,用于實現(xiàn)更加強大的功能舷胜。

六、多繼承的實現(xiàn)思路:Runtime

我們會發(fā)現(xiàn)Runtime消息轉(zhuǎn)發(fā)的一個特點:一個對象可以調(diào)起它本身不具備的方法活翩。這個過程與OC中的繼承特性很相似烹骨,其實官方文檔中圖示也很好的說明了這個問題:

forwarding.png

圖中的Warrior通過forwardInvocation:將negotiate消息轉(zhuǎn)發(fā)給了Diplomat,這就好像是Warrior使用了超類Diplomat的方法一樣材泄。所以從這個思路沮焕,我們可以在實際開發(fā)需求中模擬多繼承的操作。

七拉宗、最后總結(jié):

以上就是iOS運行時的基礎(chǔ)知識部分了峦树,理解Runtime的工作原理,下一篇iOS運行時Runtime應(yīng)用旦事,將總結(jié)其在實際開發(fā)中的使用空入。

其他參考鏈接
1.Objective-C Runtime Programming Guide
2.Objctive-C Runtime
3.iOS Runtime forwardInvocation的一些總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者族檬。
  • 序言:七十年代末歪赢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子单料,更是在濱河造成了極大的恐慌埋凯,老刑警劉巖点楼,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異白对,居然都是意外死亡掠廓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門甩恼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蟀瞧,“玉大人,你說我怎么就攤上這事条摸≡梦郏” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵钉蒲,是天一觀的道長切端。 經(jīng)常有香客問我,道長顷啼,這世上最難降的妖魔是什么踏枣? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮钙蒙,結(jié)果婚禮上茵瀑,老公的妹妹穿的比我還像新娘。我一直安慰自己躬厌,他們只是感情好马昨,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著烤咧,像睡著了一般偏陪。 火紅的嫁衣襯著肌膚如雪抢呆。 梳的紋絲不亂的頭發(fā)上煮嫌,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音抱虐,去河邊找鬼昌阿。 笑死,一個胖子當(dāng)著我的面吹牛恳邀,可吹牛的內(nèi)容都是我干的懦冰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼谣沸,長吁一口氣:“原來是場噩夢啊……” “哼刷钢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乳附,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤内地,失蹤者是張志新(化名)和其女友劉穎伴澄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阱缓,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡非凌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了荆针。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敞嗡。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖航背,靈堂內(nèi)的尸體忽然破棺而出喉悴,到底是詐尸還是另有隱情,我是刑警寧澤沃粗,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布粥惧,位于F島的核電站,受9級特大地震影響最盅,放射性物質(zhì)發(fā)生泄漏突雪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一涡贱、第九天 我趴在偏房一處隱蔽的房頂上張望咏删。 院中可真熱鬧,春花似錦问词、人聲如沸督函。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辰狡。三九已至,卻和暖如春垄分,著一層夾襖步出監(jiān)牢的瞬間宛篇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工薄湿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叫倍,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓豺瘤,卻偏偏與公主長得像吆倦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子坐求,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容