接著上一篇Runtime的執(zhí)行過程(一)许起,之前說了runtime的交互莉撇、runtime的消息機制皿曲、傳遞棵红、方法地址獲取噪舀。
四垢油、Dynamic Method Resolution
? 動態(tài)方法解析静汤,就是runtime提供了給類動態(tài)添加方法的實現(xiàn)琅催。比如類的屬性,會自動添加setter和getter方法就是如此虫给,在編譯時藤抡,將動態(tài)的添加與該屬性相關聯(lián)的setter和getter方法。
?可以通過實現(xiàn)這些方法resolveInstanceMethod:
抹估,resolveClassMethod:
缠黍,并分別為實例和類方法動態(tài)提供給定選擇器的實現(xiàn)。
/// C函數(shù)药蜻,本身就是一個IMP指針
void dynamicMethodIMP(id self瓷式,SEL _cmd){
//實施....
}
@implementation MyClass
+(BOOL)resolveInstanceMethod:(SEL)aSEL
{
if(aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP)dynamicMethodIMP, “v@:”);
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
我們看到,動態(tài)添加方法使用C函數(shù):
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
第一個參數(shù)是類语泽,即當前對象的類贸典。
第二個是一個方法選擇器,我理解就是一個方法名踱卵。
第三個是一個指向實現(xiàn)的函數(shù)指針廊驼,如果是C函數(shù)直接強轉即可,如果是OC方法惋砂,則需要獲取蔬充。可以使用以下方法獲劝嗬:
IMP imp = class_getMethodImplementation([receiver class], @selector(message));
或者
Method m = class_getInstanceMethod([receiver class], @selector(message));
IMP imp = method_getImplementation(m);
第四個是方法的編碼饥漫,一組描述方法、返回值及參數(shù)類型特征的字符罗标。
? 比如加入的方法是int sum(id self, SEL _cmd, int a, int b)
庸队,那么這里是:class_addMethod([receiver class], @selector(sum), (IMP) sum, "i@:ii");
,i
表示int(An int闯割,返回值類型)彻消,@
表示self(An object,Object類型)宙拉,:
表示_cmd(A method selector (SEL)宾尚,方法選擇器類型),ii
分別表示后面兩個參數(shù)(An int,int類型)煌贴。這里蘋果有提供一個類型對照表:
通常消息轉發(fā)和動態(tài)方法解析是互不相干的御板。在進入消息轉發(fā)機制之前, respondsToSelector:和instancesRespondToSelector: 會被首先調用牛郑。您可以在這兩個 方法中為傳進來的選標提供一個IMP怠肋。如果您實現(xiàn)了resolveInstanceMethod:方法但是仍然希望正 常的消息轉發(fā)機制進行,您只需要返回NO就可以了淹朋。
Dynamic Loading
動態(tài)加載可以用在很多地方笙各。例如,系統(tǒng)配置中的模塊就是被動態(tài)加載的础芍。
在 Cocoa 環(huán)境中杈抢,動態(tài)加載一般被用來對應用程序進行定制。您的程序可以在運行時加載其他程序員編寫的模塊——和 Interface Build 載入定制的調色板以及系統(tǒng)配置程序載入定制的模塊的類似仑性。 這些模塊通過 您許可的方式擴展了您的程序春感,而您無需自己來定義或者實現(xiàn)。您提供了框架虏缸,而其它的程序員提供了實 現(xiàn)。
盡管已經有一個運行時系統(tǒng)的函數(shù)來動態(tài)加載Mach-O文件中的Objective-C模塊 (objc_loadModules嫩实,在objc/objc-load.h中定義)刽辙,Cocoa的NSBundle類為動態(tài)加載 提供了一個更方便的接口——一個面向對象的,已和相關服務集成的接口甲献。關于NSBundle類的更多相關
信息請參考Foundation框架中關于NSBundle類的文檔宰缤。關于Mach-O文件的有關信息請參考Mac OS X ABI Mach-O 文件格式參考庫。
五晃洒、Message Forwarding
通常慨灭,給一個對象發(fā)送它不能處理的消息會得到出錯提示,然而球及,Objective-C 運行時系統(tǒng)在拋出錯誤之前氧骤, 會給消息接收對象發(fā)送一條特別的消息來通知該對象。
如果一個對象收到一條無法處理的消息吃引,運行時系統(tǒng)會在拋出錯誤前筹陵,給該對象發(fā)送一條
forwardInvocation:消息,該消息的唯一參數(shù)是個 NSInvocation 類型的對象——該對象封裝了 原始的消息和消息的參數(shù)镊尺。
您可以實現(xiàn) forwardInvocation:方法來對不能處理的消息做一些默認的處理朦佩,也可以以其它的某種 方式來避免錯誤被拋出。如 forwardInvocation:的名字所示庐氮,它通常用來將消息轉發(fā)給其它的對象语稠。
為了方便我們理解,文檔給了這么一個場景:首先弄砍,假設您正在設計一個可以響應方法名為 negotiate 的消息的對象仙畦,并且你希望它的響應跟另外一個對象一樣输涕。這時候,你可以通過將 negotiate 消息傳遞給你已經實現(xiàn)了 negotiate 方法主體中某處的其他對象來輕松完成此操作议泵。
?更進一步占贫,假設你希望對象對 negotiate 消息的響應與在另一個類中實現(xiàn)的響應完全相同。實現(xiàn)此目的的一種方法是讓你的類從另一個類繼承該方法先口。然而型奥,這樣安排事情可能是不可能的。你的類和實現(xiàn)協(xié)商的類在繼承層次結構的不同分支中可能有很好的理由碉京。
?這個時候厢汹,即使你的類不能繼承negotiate方法,你仍然可以通過實現(xiàn)一個簡單地將消息傳遞給另一個類的實例的方法版本來“借用”它:
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
這是時候谐宙,調用** negotiate**就會間接調用了someOtherObject的negotiate方法烫葬。
但是,如果當你需要將大量消息傳遞給另一個對象時凡蜻,這種方式就會變得很麻煩搭综,因為你必須提前寫好方法所有你想轉發(fā)的方法集,但是你現(xiàn)在可能并不知道完整的方法集划栓。
?forwardInvocation: 消息為這個問題提供了一個不太特別的第二次機會解決方案兑巾,而且是動態(tài)的。它的觸發(fā)時機是忠荞,當一個對象因為沒有與消息中的選擇器匹配的方法而無法響應消息時蒋歌,運行時系統(tǒng)通過向它發(fā)送一個 forwardInvocation: 消息來通知該對象。每個對象都從 NSObject 類繼承一個 forwardInvocation: 方法委煤。然而堂油,NSObject 版本的方法只是調用了 dosNotRecognizeSelector:。通過覆蓋 NSObject 的版本并實現(xiàn)您自己的版本碧绞,你可以利用 forwardInvocation: 消息提供的機會將消息轉發(fā)到其他對象府框。
要轉發(fā)消息,所有 forwardInvocation: 方法需要做的是:
1讥邻、 確定消息應該去哪里
2寓免、 將其連同其原始參數(shù)發(fā)送到那里〖莆可以使用 invokeWithTarget: 方法發(fā)送消息:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
Forwarding and Multiple Inheritance (消息轉發(fā)和多繼承)
消息轉發(fā)模仿繼承袜香,可用于將多重繼承的某些效果借給 Objective-C 程序。如圖所示鲫惶,通過轉發(fā)消息來響應消息的對象似乎借用或“繼承”了另一個類中定義的方法實現(xiàn)蜈首。(相當于OC借用消息轉發(fā)實現(xiàn)了類似C++中的多繼承)
在此這個圖中,Warrior 類的一個實例將 negotiate 消息轉發(fā)給 Diplomat 類的一個實例。Warrior看起來像外交官一樣談判欢策。它似乎會回應談判信息吆寨,并且出于所有實際目的,它確實會做出回應(盡管它實際上是一名外交官在做這項工作)踩寇。
實際上啄清,消息轉發(fā)也的確獲得了多繼承的大部分功能,但是他們本身還是有區(qū)別的:
- 多重繼承在單個對象中結合了不同的功能俺孙。它傾向于大型的辣卒、多方面的對象。
- 轉發(fā)將不同的職責分配給不同的對象睛榄。它將問題分解為更小的對象荣茫,但以對消息發(fā)送者透明的方式關聯(lián)這些對象
Forwarding and Inheritance (消息轉發(fā)和繼承)
盡管轉發(fā)模仿繼承,但 NSObject 類從不混淆兩者场靴。像 respondsToSelector: 和 isKindOfClass: 這樣的方法只看繼承層次啡莉,從不看轉發(fā)鏈。例如旨剥,如果詢問 Warrior 對象是否響應協(xié)商消息咧欣,
if ( [aWarrior RespondsToSelector:@selector(negotiate)] ) {
return NO;
}
答案是無法響應,即使它可以毫無錯誤地接收 negotiate 消息并在某種意義上通過將它們轉發(fā)給外交官來響應它們轨帜。如果你想讓你的對象表現(xiàn)得好像它們真的繼承了它們轉發(fā)消息的對象的行為魄咕,你需要重新實現(xiàn) respondsToSelector: 和 isKindOfClass: 方法來包含你的轉發(fā)算法。
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
if ([aSelector isEqualTo:@selector(negotiate)]) {
return [[someOtherObject respondsToSelector:aSelector]];
}
}
return NO;
}
除了 respondsToSelector: 和 isKindOfClass: 之外阵谚,instanceRespondToSelector: 方法也需要反映轉發(fā)算法。如果使用協(xié)議烟具,則應將conformsToProtocol: 方法同樣添加到列表中梢什。
??類似地,如果一個對象轉發(fā)它接收到的任何遠程消息朝聋,它應該有一個 methodSignatureForSelector 版本:它可以返回最終響應轉發(fā)消息的方法的準確描述嗡午;例如,如果一個對象能夠將消息轉發(fā)給它的代理冀痕,您將實現(xiàn) methodSignatureForSelector:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
蘋果提醒我們荔睹,消息轉發(fā)不是為了作為多繼承的替代品,僅僅適用于沒有其他解決方案的情況言蛇。如果確實要使用的話僻他,需要確保你真的理解進行轉發(fā)的類和轉發(fā)到類的行為。
Note: This is an advanced technique, suitable only for situations where no other solution is possible. It is
not intended as a replacement for inheritance. If you must make use of this technique, make sure you
fully understand the behavior of the class doing the forwarding and the class you’re forwarding to.