深入研究Runtime(3)
本文介紹runtime的兩個進階用法:
- 動態方法解析:如何動態地提供
方法的實現
? - 消息轉發:發送消息時,如果消息接收者沒有實現該方法,則運行時會報錯。當消息接收者沒有實現該方法時,應該如何實現將消息轉發給
另一個
接收者?
動態方法解析
有一些情況,可能是需要動態提供方法的實現的,如下:
- 編譯器指令
@dynamic
:在 xcode4.5 之前,@property
只是聲明了 setter/getter 方法,如果不使用@synthesize
,則必須自己提供 setter/getter 或者 getter 方法。另外一個就是@dynamic
,但是需動態添加 getter/setter 方法的實現。
如何給指定方法動態添加實現?
-
在
resolveInstanceMethod:
和resolveClassMethod:
中根據給定的方法名,使用class_addMethod( )
把函數作為一個方法添加到類里。void dynamicMethodIMP(id self, SEL _cmd) { // 若方法有參數,則需在后面添加相應參數 // implementation ... 方法的實現 }
-
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
- cls:需要添加方法的類
- name:方法名
- imp:方法實現對應的函數,必須有 self 和 _cmd 兩個參數
- types:C語言字符串,描述實現函數對應的參數類型( 包括返回值,第1個為描述返回值,因為2、3分別是id self,SEL _cmd,所以必須為"@:" )
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) { // 是否為resolveThisMethodDynamically方法
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); // 動態添加方法
return YES; // 返回YES,則不會轉發
}
return [super resolveInstanceMethod:aSEL]; // 不是需要動態添加的方法,按照父類處理
}
@end
動態添加方法實現與消息的轉發是不可以同時存在的,動態添加優先于消息轉發。如果實現了
resolveInstanceMethod:
和resolveClassMethod:
,并且返回YES,則不會進行消息的轉發。如果沒有實現,則轉發消息。
補充:動態加載
Objective-C的程序允許在運行時加載和鏈接新的類與分類,此時加載與開始時加載無異。
動態加載類與分類一個比較重要的應用就是加載動態庫
。
- 使用方法:
- 使用運行時函數
objc_loadModules
( 在objc/objc-load.h
頭文件中定義 ),動態加載Mach-O
文件里的Objective-C的功能模塊(動態庫) - NSBundle類中提供了簡便的接口進行動態加載
Mach-O
文件
- 使用運行時函數
Mach-O
文件:是Mac和iOS系統的可執行二進制文件,類似于window中的.exe文件
消息轉發
當給對象發送消息時,如果該對象沒有實現相應的方法處理該消息,則會調用 -forwardInvocation:
方法。NSObject類中實現了該方法,默認調用 -doesNotRecognizeSelector:
方法,所以如果繼承于NSObject的類接收沒有實現的消息,則會報運行時錯誤
。
所以,在-forwardInvocation:
方法中實現消息轉發即可。
-
轉發一條消息,在
-forwardInvocation:
方法中需要做的事情:- 決定把消息轉發給誰
-
傳遞原始的參數
在
-forwardInvocation:
方法中,會傳遞給該方法唯一一個參數,NSInvocation的對象( 包含了原始的消息和參數 )- (void)forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector:[anInvocation selector]]) // 如果指定對象實現了該方法 [anInvocation invokeWithTarget:someOtherObject]; // 給指定對象發送消息 else [super forwardInvocation:anInvocation]; // 按照父類處理 }
轉發與多重繼承
如下圖所示:
轉發.png
Warrior類沒有實現negotiate
方法。當接收negotiate
消息時,會調用Diplomat的對象方法,并且會傳遞原始參數。所以,這個過程類似于繼承調用方法的過程。而Warrior本來就有父類,所以這種現象相當于使用轉發機制
“ 實現了“多重繼承。
轉發與繼承的對比:
繼承是把所有父類的“能力”都集中到一個類中,而轉發只是僅僅調用了一下方法,把問題分離出去。在一定程度上還是有很大區別的。
轉發與繼承
轉發是模仿繼承的。但是NSObject永遠不會混淆這兩個概念。就好像方法-respondsToSelector:
和-isKindOfClass:
只會在繼承層次中有效,而在轉發鏈中是無效的,永遠只會返回NO
。
但是有些情況下,你也想轉發鏈中返回的是YES
。那樣就必須重寫-respondsToSelector:
和-isKindOfClass:
方法。
如下重寫-respondsToSelector:
方法:
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector] ) // 如果父類實現了,則返回YES
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. */
}
return NO;
}
當你如果想將轉發完全模仿了繼承,重寫-respondsToSelector:
和-isKindOfClass:
方法是遠遠不夠的。
以下是大概上還需要重寫的方法:
instancesRespondToSelector:
- 如果使用了協議,則需重寫
conformsToProtocol:
- 重寫
methodSignatureForSelector:
方法,返回轉發后的方法描述- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; // 如果父類實現了該方法,才會有方法的描述,返回父類的即可 if (!signature) { // 若沒有,則返回轉發對象的 signature = [surrogate methodSignatureForSelector:selector]; } return signature; }
note:這是一個高級別的技術點,當真的沒有其它解決方案時才會考慮使用轉發機制。千萬不要用來替代繼承,先考慮使用繼承。如果真的需要使用,要保證自己對
實行轉發
和接收轉發
這兩個類的所有行為有一個完全的理解。
來自: http://www.jianshu.com/p/334e2605e4ee