Objective-C 的“多繼承”
當單繼承不夠用,很難為問題域建模時,我們通常都會直接想到多繼承。多繼承是從多餘一個直接基類衍生類別的能力,可以更加直接地為應用程式建模。但是Objective-C不支援多繼承,由於訊息機制名字尋找發生在運行時而非編譯時間,很難解決多個基類可能導致的二義性問題。不過其實 Objective-C 也無需支援多繼承,我們可以找到如下幾種間接實現多繼承目的的方法:
- 訊息轉寄
- delegate和protocol
- 類別
訊息轉寄
當向someObject發送某訊息,但runtime system在當前類和父類中都找不到對應方法的實現時,runtime system並不會立即報錯使程式崩潰,而是依次執行下列步驟:
分別簡述一下流程:
1.動態方法解析
向當前類發送 resolveInstanceMethod: 訊號,檢查是否動態向該類添加了方法。(迷茫請搜尋:@dynamic) 2.快速訊息轉寄
檢查該類是否實現了 forwardingTargetForSelector: 方法,若實現了則調用這個方法。若該方法傳回值對象非nil或非self,則向該返回對象重新發送訊息。
3.標準訊息轉寄
runtime發送methodSignatureForSelector:訊息擷取Selector對應的方法簽名。傳回值非空則通過forwardInvocation:轉寄訊息,傳回值為空白則向當前對象發送doesNotRecognizeSelector:訊息,程式崩潰退出。
顧名思義,我們可以利用上述過程中的2、3兩種方式來完成訊息轉寄。 快速訊息轉寄
快速訊息轉寄的實現方法很簡單,只需要重寫 - (id)forwardingTargetForSelector:(SEL)aSelector 方法即可。
我來舉個簡單的例子,比如現有2個類:Teacher 和 Doctor,Doctor可以做手術(operate方法)。
@interface Teacher : NSObject@end
@interface Doctor : NSObject- (void)operate;@end
通過快速訊息轉寄,可以很輕鬆的讓teacher調用doctor的方法做手術。
Teacher類需要實現將訊息轉寄給Doctor:
- (id)forwardingTargetForSelector:(SEL)aSelector{ Doctor *doctor = [[Doctor alloc]init];if ([doctor respondsToSelector:aSelector]) {return doctor;}return nil;}
雖然訊息可以動態轉寄傳遞,但是編輯器的靜態檢查是繞不過的,那麼問題來了,既然Teacher類沒有實現operate方法又該如何聲明呢?
到目前為止,我只想到下面2種方法:
聲明方法1 ———— 類別
@interface Teacher (DoctorMethod)- (void)operate;@end
聲明方法2 ———— 匯入標頭檔、調用時強轉類型
Teacher類標頭檔需要包含Doctor標頭檔,告訴編譯器去Doctor.h中可以找到operator方法的聲明,並且在調用時強轉類型。
Teacher *teacher = [[Teacher alloc]init]; [(Doctor *)teacher operate];
有興趣可以思考一個問題:如果將其類型轉成 id ,也可以編譯通過,並實現轉寄。可是會帶來什麼隱患呢?
方法1使用類別足夠清晰簡便,為什麼還要提出辦法2呢 ? 我的想法是,方法1的弊端是拋出來的方法是定死的,而且在.h裡露著;方法2就相對靈活,而且隱藏了我要轉寄的訊息。
標準訊息轉寄
標準訊息轉寄需要重寫 methodSignatureForSelector: 和 forwardInvocation: 兩個方法即可。
發流程:
轉寄重寫方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSMethodSignature* signature = [super methodSignatureForSelector:aSelector]; if (signature==nil) { signature = [someObj methodSignatureForSelector:aSelector]; } NSUInteger argCount = [signature numberOfArguments]; for (NSInteger i=0 ; i<argCount ; i++) { } return signature;}- (void)forwardInvocation:(NSInvocation *)anInvocation{ SEL seletor = [anInvocation selector]; if ([someObj respondsToSelector:seletor]) { [anInvocation invokeWithTarget:someObj]; } }
兩種訊息轉寄方式的比較
快速訊息轉寄:簡單、快速、但僅能轉寄給一個對象。
標準訊息轉寄:稍複雜、較慢、但轉寄操作實現可控,可以實現多個物件轉寄。
delegate和protocol
委託是Objective-C中最常用的一種回調機制。用法我覺得沒什麼好說的,總結一下該機制特點:
- 委託協助主體完成操作任務,將需要定製化的操作預留給委派物件來自訂實現,類似子類化主體。
- 除此之外,可以用作事件監聽。
- 一時還真想不出來了…
類別
個人認為類別是Objective-C設計的一大精髓,也是我愛上Objective-C的最大理由。
類別是個強大的東西,它既可以為類添加方法,也可以添加執行個體。一定有不少人不認同,想提醒我:類別的局限性之一就是無法向類中添加新的執行個體變數。 背書真心毀人,聽我舉個例子慢慢說。
重新再來個Teacher類:
@interface Teacher : NSObject{ NSUInteger age;}@end
光有個年齡還不能滿足對teacher的描述,我想加個profession執行個體來存teacher的專業。直觀的想法是子類化Teacher,其實也可以用類別。 你需要瞭解一下 runtime 編程知識,關注一下 objc_setAssociatedObject 和 objc_getAssociatedObject 。
//// Teacher+Profession.m// #import "Teacher+Profession.h"#import <objc/runtime.h>const char *ProfessionType = "NSString *";@implementation Teacher (Profession)-(void)setProf:(NSString*)prof{ objc_setAssociatedObject(self, ProfessionType, prof, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}-(NSString *)prof{ NSString *pro = objc_getAssociatedObject(self, ProfessionType); return pro;}@end
現在就可以通過setProf: 和 prof 來存取 teacher 的 profession 值了。
寫在最後
我很願意看到你的反饋,任何指正、批評及完善方案,讓我們共同進步。