iOS之Runtime(一)

一:@@@《基礎(chǔ)篇》@@@

二:@@@《應(yīng)用篇》@@@

目錄:

一拄氯、理解OC是動(dòng)態(tài)語(yǔ)言,Runtime又是什么它浅?
二、消息機(jī)制的基本原理
三、與Runtime交互的三種方式
四义钉、分析Runtime中的數(shù)據(jù)結(jié)構(gòu)
五盯荤、深入理解Rutime消息發(fā)送原理
六、多繼承的實(shí)現(xiàn)思路:Runtime

題外話(huà):計(jì)算機(jī)唯一能識(shí)別的語(yǔ)言是機(jī)器語(yǔ)言镊折,高級(jí)編程語(yǔ)言不能被直接識(shí)別胯府,需要先編譯為匯編語(yǔ)言,再由匯編語(yǔ)言編譯為機(jī)器語(yǔ)言才能被計(jì)算機(jī)識(shí)別恨胚。而 Objective-C語(yǔ)言不能被直接編譯為匯編語(yǔ)言骂因,它必須先編譯為C語(yǔ)言,然后再編譯為匯編語(yǔ)言赃泡,最后再由匯編語(yǔ)言編譯為機(jī)器語(yǔ)言才能被計(jì)算機(jī)識(shí)別寒波。 從OC到C語(yǔ)言的過(guò)渡就是由runtime來(lái)實(shí)現(xiàn)的。我們使用OC進(jìn)行面向?qū)ο箝_(kāi)發(fā)升熊,但是C語(yǔ)言更多的是面向過(guò)程開(kāi)發(fā)俄烁,這就需要將面向?qū)ο蟮念?lèi)轉(zhuǎn)變?yōu)槊嫦蜻^(guò)程的結(jié)構(gòu)體。

一级野、理解OC是動(dòng)態(tài)語(yǔ)言页屠,Runtime又是什么?

靜態(tài)語(yǔ)言:如C語(yǔ)言,編譯階段就要決定調(diào)用哪個(gè)函數(shù)卷中,如果函數(shù)未實(shí)現(xiàn)就會(huì)編譯報(bào)錯(cuò)矛双。

動(dòng)態(tài)語(yǔ)言:如OC語(yǔ)言,編譯階段并不能決定真正調(diào)用哪個(gè)函數(shù)蟆豫,只要函數(shù)聲明過(guò)即使沒(méi)有實(shí)現(xiàn)也不會(huì)報(bào)錯(cuò)议忽。

我們常說(shuō)OC是一門(mén)動(dòng)態(tài)語(yǔ)言,就是因?yàn)樗偸前岩恍Q定性的工作(數(shù)據(jù)類(lèi)型的確定)從編譯階段推遲到運(yùn)行時(shí)階段十减。OC代碼的運(yùn)行不僅需要編譯器栈幸,還需要運(yùn)行時(shí)系統(tǒng)(Runtime Sytem)來(lái)執(zhí)行編譯后的代碼。

Runtime 是一套底層純C語(yǔ)言API帮辟,OC代碼最終都會(huì)被編譯器轉(zhuǎn)化為運(yùn)行時(shí)代碼速址,通過(guò)消息機(jī)制決定函數(shù)調(diào)用方式,這也是OC作為動(dòng)態(tài)語(yǔ)言使用的基礎(chǔ)由驹。

二芍锚、消息機(jī)制的基本原理

OC 的方法調(diào)用都是類(lèi)似 [self methodName] 的形式,其實(shí)每次都是一個(gè)運(yùn)行時(shí)消息發(fā)送過(guò)程蔓榄。

第一步:編譯階段
[self methodName] 方法被編譯器轉(zhuǎn)化并炮,分為兩種情況:
1. 不帶參數(shù)的方法被編譯為:objc_msgSend(receiver,methodName)
2. 帶參數(shù)的方法被編譯為:objc_msgSend(recevier甥郑,methodName逃魄,org1,org2澜搅,…)
 
第二步:運(yùn)行時(shí)階段
消息接收者 recever 尋找對(duì)應(yīng)的 selector(方法)伍俘,也分為兩種情況:
1.接收者能找到對(duì)應(yīng)的selector,直接執(zhí)行接收receiver對(duì)象的selector方法勉躺。
2.接收者找不到對(duì)應(yīng)的selector癌瘾,消息被轉(zhuǎn)發(fā)或者臨時(shí)向接收者添加這個(gè)selector對(duì)應(yīng)的實(shí)現(xiàn)內(nèi)容,否則崩潰饵溅。

說(shuō)明:OC 調(diào)用方法[self methodName]柳弄,編譯階段確定了要向哪個(gè)接收者發(fā)送message消息,但是接收者如何響應(yīng)決定于運(yùn)行時(shí)的判斷概说。

三碧注、與Runtime 交互的三種方式

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

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

之前已經(jīng)說(shuō)過(guò)放典,OC代碼會(huì)在編譯階段被編譯器轉(zhuǎn)化逝变。OC中的類(lèi)基茵、方法和協(xié)議等在Runtime中都由一些數(shù)據(jù)結(jié)構(gòu)來(lái)定義。所以壳影,我們平時(shí)直接使用OC編寫(xiě)代碼拱层,其實(shí)這已經(jīng)是在和Runtime進(jìn)行交互了,只不過(guò)這個(gè)過(guò)程對(duì)于我們來(lái)說(shuō)是無(wú)感的宴咧。

2. NSObject方法(NSObject Methods)

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

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

-class //方法返回對(duì)象的類(lèi)氧卧;

-isKindOfClass: 和 -isMemberOfClass:  //檢查對(duì)象是否存在于指定的類(lèi)的繼承體系中

-respondsToSelector:    //檢查對(duì)象能否響應(yīng)指定的消息桃笙;

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

-methodForSelector:     //返回指定方法實(shí)現(xiàn)的地址沙绝。

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

Runtime系統(tǒng)是一個(gè)由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成搏明,具有公共接口的動(dòng)態(tài)共享庫(kù)。頭文件存放于/usr/include/objc目錄下闪檬。在我們工程代碼里引用Runtime的頭文件星著,同樣能夠?qū)崿F(xiàn)類(lèi)似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語(yǔ)言强饮,然后再通過(guò)運(yùn)行時(shí)執(zhí)行由桌,最終實(shí)現(xiàn)了動(dòng)態(tài)調(diào)用为黎。這其中的OC類(lèi)、對(duì)象和方法等都對(duì)應(yīng)了C中的結(jié)構(gòu)體行您,而且我們都可以在Rutime源碼中找到它們的定義铭乾。

要查看Runtime的代碼 ,只需要我們?cè)诋?dāng)前代碼文件中導(dǎo)入如下頭文件,使用組合鍵"Command +鼠標(biāo)點(diǎn)擊"娃循,即可進(jìn)入Runtime的源碼文件炕檩。

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

1. id 對(duì)應(yīng): objc_object

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

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

id 在OC中是表示 一個(gè)任意類(lèi)型的類(lèi)實(shí)例捌斧。從這里也可以看出笛质,OC中的對(duì)象雖然沒(méi)有明顯的使用指針,但是在OC代碼被編譯轉(zhuǎn)化為C之后捞蚂,每個(gè)OC對(duì)象其實(shí)都是擁有一個(gè)isa的指針的妇押。

2. Class 對(duì)應(yīng): objc_classs

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

typedef struct objc_class *Class; 

下面是 Runtime 中對(duì) 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                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
理解 objc_class 定義中的參數(shù):

1.isa指針:

objc_object 和 objc_class 同樣是結(jié)構(gòu)體姓迅,而且都擁有一個(gè)isa指針敲霍。

objc_object 的isa指針指向?qū)ο蟮亩x俊马,那么objc_class的指針是怎么回事呢?

其實(shí)肩杈,在Runtime中Objc類(lèi)本身同時(shí)也是一個(gè)對(duì)象柴我。Runtime把類(lèi)對(duì)象所屬類(lèi)型就叫做元類(lèi),用于描述類(lèi)對(duì)象本身所具有的特征扩然,最常見(jiàn)的類(lèi)方法就被定義于此艘儒,所以objc_class中的isa指針指向的是元類(lèi),每個(gè)類(lèi)僅有一個(gè)類(lèi)對(duì)象与学,而每個(gè)類(lèi)對(duì)象僅有一個(gè)與之相關(guān)的元類(lèi)彤悔。

2. super_class 指針:

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

3. cache:

為了優(yōu)化性能索守,objc_class 中的cache結(jié)構(gòu)體用于記錄每次使用類(lèi)或者實(shí)例對(duì)象調(diào)用的方法晕窑。這樣每次響應(yīng)消息的時(shí)候,Runtime系統(tǒng)會(huì)優(yōu)先在cache中尋找響應(yīng)方法卵佛,相比直接在類(lèi)的方法列表中遍歷查找杨赤,效率更高。

4. ivars:

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

5. methodLists:

methodLists 用于存放對(duì)象的所有成員方法。

6. protocols:

protocols 用于存放對(duì)象的所有協(xié)議衙解。

3. SEL 對(duì)應(yīng): objc_selector

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

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

SEL在OC中稱(chēng)作方法選擇器,用于表示運(yùn)行時(shí)方法的名字蚓峦,然而我們并不能在Runtime中找到它的結(jié)構(gòu)體的詳細(xì)定義舌剂。Objective-C在編譯時(shí),會(huì)依據(jù)每一個(gè)方法的名字暑椰、參數(shù)序列霍转,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類(lèi)型的地址),這個(gè)標(biāo)識(shí)就是SEL一汽。

注意

1.不同類(lèi)中相同名字的方法對(duì)應(yīng)的方法選擇器是相同的避消。

2.即使是同一個(gè)類(lèi)中,方法名相同而變量類(lèi)型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器召夹。

通常我們獲取SEL有三種方法
1.OC中岩喷,使用@selector(“方法名字符串”)
2.OC中,使用NSSelectorFromString(“方法名字符串”)
3.Runtime方法监憎,使用sel_registerName(“方法名字符串”)

4. Ivar 對(duì)應(yīng): objc_ivar

Ivar 代表類(lèi)中實(shí)例變量的類(lèi)型纱意,是一個(gè)指向 objc_ivar 的結(jié)構(gòu)體的指針,即在Runtime中:

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

Runtime 中對(duì) objc_ivar 結(jié)構(gòu)體的具體定義:

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}  

在 objc_class 中看到的ivars成員列表,其中的元素就是Ivar枫虏,我可以通過(guò)實(shí)例查找其在類(lèi)中的名字妇穴,這個(gè)過(guò)程被稱(chēng)為反射爬虱,下面的 class_copyIvarList 獲取的不僅有實(shí)例變量還有屬性:

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 對(duì)應(yīng): objc_method

Method 表示某個(gè)方法的類(lèi)型腾它,是一個(gè)指向 objc_method 的結(jié)構(gòu)體的指針跑筝,即在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                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}     

理解 objc_method 定義中的參數(shù):
method_name:方法名類(lèi)型SEL
method_types: 一個(gè)char指針曲梗,指向存儲(chǔ)方法的參數(shù)類(lèi)型和返回值類(lèi)型
method_imp:本質(zhì)上是一個(gè)指針,指向方法的實(shí)現(xiàn)
這里其實(shí)就是SEL(method_name)與IMP(method_name)形成了一個(gè)映射妓忍,通過(guò)SEL虏两,我們可以很方便的找到方法實(shí)現(xiàn)IMP。

6. IMP

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

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

IMP這個(gè)函數(shù)指針指向了方法實(shí)現(xiàn)的首地址定罢,當(dāng)OC發(fā)起消息后,最終執(zhí)行的代碼是由IMP指針決定的旁瘫。利用這個(gè)特性祖凫,我們可以對(duì)代碼進(jìn)行優(yōu)化:當(dāng)需要大量重復(fù)調(diào)用方法的時(shí)候,我們可以繞開(kāi)消息綁定而直接利用IMP指針調(diào)起方法酬凳,這樣的執(zhí)行將會(huì)更加高效惠况,相關(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ù)指針(IMP)的前兩個(gè)參數(shù)必須是 id 和 SEL。

五宁仔、深入理解Rutime消息發(fā)送

先前講到稠屠,OC調(diào)用方法被編譯轉(zhuǎn)化為如下的形式:

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

其實(shí),除了常見(jiàn)的 objc_msgSend翎苫,消息發(fā)送的方法還有objc_msgSend_stret , objc_msgSendSuper , objc_msgSendSuper_stret 等权埠,如果消息傳遞給超類(lèi)就使用帶有super的方法,如果返回值是結(jié)構(gòu)體而不是簡(jiǎn)單值就使用帶有stret的值拉队。

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

  1. 檢測(cè)selector 是不是需要忽略的弊知。比如 Mac OS X 開(kāi)發(fā)阻逮,有了垃圾回收就不理會(huì)retain,release 這些函數(shù)了粱快。

  2. 檢測(cè)target 是不是nil 對(duì)象。ObjC 的特性是允許對(duì)一個(gè) nil對(duì)象執(zhí)行任何一個(gè)方法不會(huì) Crash叔扼,因?yàn)闀?huì)被忽略掉事哭。

  3. 如果上面兩個(gè)都過(guò)了,那就開(kāi)始查找這個(gè)類(lèi)的 IMP瓜富,先從 cache 里面找鳍咱,若可以找得到就跳到對(duì)應(yīng)的函數(shù)去執(zhí)行。

  4. 如果在cache里找不到就找一下方法列表methodLists与柑。

  5. 如果methodLists找不到谤辜,就到超類(lèi)的方法列表里尋找蓄坏,一直找,直到找到NSObject類(lèi)為止丑念。

  6. 如果還找不到涡戳,Runtime就提供了如下三種方法來(lái)處理:動(dòng)態(tài)方法解析消息接受者重定向脯倚、消息重定向渔彰,這三種方法的調(diào)用關(guān)系如下圖:

    image.png

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

所謂動(dòng)態(tài)解析: 通過(guò)cache和方法列表沒(méi)有找到方法時(shí),Runtime為我們提供一次動(dòng)態(tài)添加方法實(shí)現(xiàn)的機(jī)會(huì)推正。主要用到的方法如下:

// OC方法:

// 類(lèi)方法未找到時(shí)調(diào)起恍涂,可于此添加類(lèi)方法實(shí)現(xiàn)
+ (BOOL)resolveClassMethod:(SEL)sel
  
//實(shí)例方法未找到時(shí)調(diào)起,可于此添加實(shí)例方法實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel

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

下面使用一個(gè)示例來(lái)說(shuō)明動(dòng)態(tài)解析:MyGirl 類(lèi)中聲明方法卻未添加實(shí)現(xiàn)植榕,我們通過(guò)Runtime動(dòng)態(tài)方法解析的操作為其他添加方法實(shí)現(xiàn)再沧,具體代碼如下:

// MyGirl.h文件
#import <Foundation/Foundation.h>

@interface MyGirl : NSObject

// 聲明類(lèi)方法,但未實(shí)現(xiàn)
+(void)myGirl:(NSString *)need;

// 聲明實(shí)例方法尊残,但未實(shí)現(xiàn)
-(void)eatFoods:(NSString *)foodsName;

@end
// MyGirl.m文件
#import "MyGirl.h"
#import <objc/runtime.h>  // 導(dǎo)入運(yùn)行時(shí)

@implementation MyGirl

// 類(lèi)方法未找到(實(shí)現(xiàn))時(shí)調(diào)起
+(BOOL)resolveClassMethod:(SEL)sel{
    
    if (sel==@selector(myGirl:)) {
        
        // 類(lèi)方法獲取類(lèi)名
        Class classValue=object_getClass(self);
        
        // 類(lèi)中添加方法
        class_addMethod(classValue, sel, class_getMethodImplementation(classValue, @selector(myGirlReplaceFun:)), "我的女孩");
        
        return YES;
    }
    return [super resolveClassMethod:sel];
}

+(void)myGirlReplaceFun:(NSString *)value{
    NSLog(@"類(lèi)方法沒(méi)有實(shí)現(xiàn),調(diào)用我啦(%@)",value);
}


// 實(shí)例方法未找到(實(shí)現(xiàn))時(shí)調(diào)起
+(BOOL)resolveInstanceMethod:(SEL)sel{
    
    if (sel==@selector(eatFoods:)) {
        
        // Class classNext=object_getClass(self); // 不能使用類(lèi)方法獲取類(lèi)名
        
        // 實(shí)例方法獲取類(lèi)名
        Class classValue=[self class];
        
        // 類(lèi)中添加方法
        class_addMethod(classValue, sel, class_getMethodImplementation(classValue, @selector(eatFoodsReplaceFun:)), "吃什么呢");
    }
    return [super resolveInstanceMethod:sel];
}

-(void)eatFoodsReplaceFun:(NSString *)foodsName{
    NSLog(@"實(shí)例方法沒(méi)有實(shí)現(xiàn),來(lái)吃我啦(%@)",foodsName);
}

@end


// 控制器中導(dǎo)入文件和調(diào)用

#import "MyGirl.h"

- (void)viewDidLoad {
    [super viewDidLoad];
    [self useRuntimeAction];
}

-(void)useRuntimeAction{
  
    // 調(diào)用類(lèi)方法
    [MyGirl myGirl:@"APink??"];
    
    // 調(diào)用實(shí)例方法
    MyGirl *girl=[[MyGirl alloc]init];
    [girl eatFoods:@"哈根達(dá)斯??"];
}

最后運(yùn)行結(jié)果:

TestModel[89579:13842797] 類(lèi)方法沒(méi)有實(shí)現(xiàn),調(diào)用我啦(APink??)
TestModel[89579:13842797] 實(shí)例方法沒(méi)有實(shí)現(xiàn),來(lái)吃我啦(哈根達(dá)斯??)

2. 消息接收者重定向

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

// 重定向類(lèi)方法的消息接收者屎即,返回一個(gè)類(lèi)
- (id)forwardingTargetForSelector:(SEL)aSelector

// 重定向?qū)嵗椒ǖ南⒔邮苷撸祷匾粋€(gè)實(shí)例對(duì)象
- (id)forwardingTargetForSelector:(SEL)aSelector

下面使用一個(gè)示例來(lái)說(shuō)明消息接收者的重定向:
我們創(chuàng)建一個(gè)MyBoy 類(lèi)事富,聲明并實(shí)現(xiàn) playGameAction:技俐、talkWithGirlfriend:兩個(gè)方法,然后在視圖控制器ViewController.h 里測(cè)試统台,關(guān)鍵代碼如下:

// MyBoy.h 文件中
#import <Foundation/Foundation.h>

@interface MyBoy : NSObject

// 類(lèi)方法
+(void)playGameAction:(NSString *)gameName;

// 實(shí)例方法
-(void)talkWithGirlfriend:(NSString *)GFName;

@end


// MyBoy.m 文件中

#import "MyBoy.h"
@implementation MyBoy

+(void)playGameAction:(NSString *)gameName{
    NSLog(@"進(jìn)來(lái)的方法:(%s) 參數(shù)值是:%@",__func__,gameName);
}

-(void)talkWithGirlfriend:(NSString *)GFName{
    NSLog(@"進(jìn)來(lái)的方法:(%s) 參數(shù)值是:%@",__func__,GFName);
}

@end
// 在ViewController.m 文件中
#import "ViewController.h"
#import "MyBoy.h"

@interface ViewController ()
  
@property (nonatomic,strong)MyBoy  *boyObj;

@end
  
- (void)viewDidLoad {
    [super viewDidLoad];
    [self useRuntimeNextAction];   
}
-(void)useRuntimeNextAction{
    // 調(diào)用并未聲明和實(shí)現(xiàn)的類(lèi)方法
    [ViewController performSelector:@selector(playGameAction:) withObject:@"極品飛車(chē)"];
    
    // 調(diào)用并未聲明和實(shí)現(xiàn)的類(lèi)方法
    self.boyObj = [[MyBoy alloc] init];
    [self performSelector:@selector(talkWithGirlfriend:) withObject:@"LYE"];
    
}
// 重定向類(lèi)方法:返回一個(gè)類(lèi)對(duì)象
+(id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(playGameAction:)) {
        return [MyBoy class];
    }
    return [super forwardingTargetForSelector:aSelector];
}
// 重定向?qū)嵗椒ǎ悍祷仡?lèi)的實(shí)例
-(id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(talkWithGirlfriend:)) {
        return self.boyObj;
    }
    return [super forwardingTargetForSelector:aSelector];
}

運(yùn)行最終結(jié)果:

TestModel[90017:13933756] 進(jìn)來(lái)的方法:(+[MyBoy playGameAction:]) 參數(shù)值是:極品飛車(chē)
TestModel[90017:13933756] 進(jìn)來(lái)的方法:(-[MyBoy talkWithGirlfriend:]) 參數(shù)值是:LYE

注意:動(dòng)態(tài)方法解析階段返回NO時(shí)雕擂,我們可以通過(guò)forwardingTargetForSelector可以修改消息的接收者,該方法返回參數(shù)是一個(gè)對(duì)象贱勃,如果這個(gè)對(duì)象是非nil井赌,非self,系統(tǒng)會(huì)將運(yùn)行的消息轉(zhuǎn)發(fā)給這個(gè)對(duì)象執(zhí)行贵扰。否則仇穗,繼續(xù)查找其他流程。

3. 消息接收者重定向

當(dāng)以上兩種方法無(wú)法生效戚绕,那么這個(gè)對(duì)象會(huì)因?yàn)檎也坏较鄳?yīng)的方法實(shí)現(xiàn)而無(wú)法響應(yīng)消息纹坐,此時(shí)Runtime系統(tǒng)會(huì)通過(guò)forwardInvocation:消息通知該對(duì)象,給予此次消息發(fā)送最后一次尋找IMP的機(jī)會(huì):

- (void)forwardInvocation:(NSInvocation *)anInvocation舞丛;

其實(shí)每個(gè)對(duì)象都從NSObject類(lèi)中繼承了forwardInvocation:方法耘子,但是NSObject中的這個(gè)方法只是簡(jiǎn)單的調(diào)用了doesNotRecongnizeSelector:方法果漾,提示我們錯(cuò)誤。所以我們可以重寫(xiě)這個(gè)方法:對(duì)不能處理的消息做一些默認(rèn)處理谷誓,也可以將消息轉(zhuǎn)發(fā)給其他對(duì)象來(lái)處理跨晴,而不拋出錯(cuò)誤。

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

我們會(huì)發(fā)現(xiàn)Runtime消息轉(zhuǎn)發(fā)的一個(gè)特點(diǎn):一個(gè)對(duì)象可以調(diào)起它本身不具備的方法端盆。這個(gè)過(guò)程與OC中的繼承特性很相似,其實(shí)官方文檔中圖示也很好的說(shuō)明了這個(gè)問(wèn)題:


image.png

圖中的Warrior通過(guò)forwardInvocation:將negotiate消息轉(zhuǎn)發(fā)給了Diplomat费封,這就好像是Warrior使用了超類(lèi)Diplomat的方法一樣焕妙。所以從這個(gè)思路,我們可以在實(shí)際開(kāi)發(fā)需求中模擬多繼承的操作弓摘。

最后,讓我們深入一點(diǎn)吧??7偃怠!韧献!

二:@@@《應(yīng)用篇》@@@

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末末患,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锤窑,更是在濱河造成了極大的恐慌璧针,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渊啰,死亡現(xiàn)場(chǎng)離奇詭異探橱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)绘证,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)隧膏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人嚷那,你說(shuō)我怎么就攤上這事胞枕。” “怎么了魏宽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵腐泻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我湖员,道長(zhǎng)贫悄,這世上最難降的妖魔是什么瑞驱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任娘摔,我火速辦了婚禮,結(jié)果婚禮上唤反,老公的妹妹穿的比我還像新娘凳寺。我一直安慰自己鸭津,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布肠缨。 她就那樣靜靜地躺著逆趋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晒奕。 梳的紋絲不亂的頭發(fā)上闻书,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音脑慧,去河邊找鬼魄眉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛闷袒,可吹牛的內(nèi)容都是我干的坑律。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼囊骤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晃择!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起也物,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宫屠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后滑蚯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體激况,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年膘魄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乌逐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡创葡,死狀恐怖浙踢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灿渴,我是刑警寧澤洛波,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站骚露,受9級(jí)特大地震影響蹬挤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜棘幸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一焰扳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦吨悍、人聲如沸扫茅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)葫隙。三九已至,卻和暖如春躏仇,著一層夾襖步出監(jiān)牢的瞬間恋脚,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工焰手, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慧起,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓册倒,卻偏偏與公主長(zhǎng)得像蚓挤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驻子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355