本文主要整理了Runtime的相關(guān)知識离熏。對于一個iOS開發(fā)者來說履婉,掌握Runtime的重要性早已不言而喻闲擦。OC能夠作為一門優(yōu)秀的動態(tài)特性語言萌壳,在其背后默默工作著的就是Runtime。在網(wǎng)上也看過很多資料摹芙,最終我還是希望在一些關(guān)鍵的知識點上能夠融入自己的理解灼狰,從簡單的問題出發(fā),一步一步理解和學(xué)以致用浮禾。
相關(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ì)步驟如下:
- 檢測selector 是不是需要忽略的巍扛。比如 Mac OS X 開發(fā),有了垃圾回收就不理會retain,release 這些函數(shù)了乏德。
- 檢測target 是不是nil 對象撤奸。ObjC 的特性是允許對一個 nil對象執(zhí)行任何一個方法不會 Crash,因為會被忽略掉喊括。
- 如果上面兩個都過了胧瓜,那就開始查找這個類的 IMP,先從 cache 里面找郑什,若可以找得到就跳到對應(yīng)的函數(shù)去執(zhí)行府喳。
- 如果在cache里找不到就找一下方法列表methodLists。
- 如果methodLists找不到蘑拯,就到超類的方法列表里尋找钝满,一直找,直到找到NSObject類為止申窘。
- 如果還找不到舱沧,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中的繼承特性很相似烹骨,其實官方文檔中圖示也很好的說明了這個問題:
圖中的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é)