一:@@@《基礎(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ì)步驟如下:
檢測(cè)selector 是不是需要忽略的弊知。比如 Mac OS X 開(kāi)發(fā)阻逮,有了垃圾回收就不理會(huì)retain,release 這些函數(shù)了粱快。
檢測(cè)target 是不是nil 對(duì)象。ObjC 的特性是允許對(duì)一個(gè) nil對(duì)象執(zhí)行任何一個(gè)方法不會(huì) Crash叔扼,因?yàn)闀?huì)被忽略掉事哭。
如果上面兩個(gè)都過(guò)了,那就開(kāi)始查找這個(gè)類(lèi)的 IMP瓜富,先從 cache 里面找鳍咱,若可以找得到就跳到對(duì)應(yīng)的函數(shù)去執(zhí)行。
如果在cache里找不到就找一下方法列表methodLists与柑。
如果methodLists找不到谤辜,就到超類(lèi)的方法列表里尋找蓄坏,一直找,直到找到NSObject類(lèi)為止丑念。
-
如果還找不到涡戳,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)題:
圖中的Warrior通過(guò)forwardInvocation:將negotiate消息轉(zhuǎn)發(fā)給了Diplomat费封,這就好像是Warrior使用了超類(lèi)Diplomat的方法一樣焕妙。所以從這個(gè)思路,我們可以在實(shí)際開(kāi)發(fā)需求中模擬多繼承的操作弓摘。