本隨筆系列主要介紹從一個Windows平臺從事C#開發(fā)到Mac平臺蘋果開發(fā)的一系列感想和體驗歷程,本系列文章是在起步階段逐步積累的,希望帶給大家更好,更真實(shí)的轉(zhuǎn)換歷程體驗。本文繼續(xù)上一篇隨筆《從C#到Object C,循序漸進(jìn)學(xué)習(xí)蘋果開發(fā)(2)--Objective-C和C#的差異》,繼續(xù)對比介紹它們兩者之間的差異,以便我們從C#陣營過來的人員加深印象,深入了解Objective-C語言的特性。本篇隨筆主要針對Objective-C里面的分類(category)和協(xié)議Protocal概念的理解進(jìn)行介紹。
1、分類(category)概念和使用
如果我們使用過C#,我們都知道,C#里面有一個叫做擴(kuò)展函數(shù)的東西,可以在不繼承已有類的情況下,給存在的類增加一些原本沒有的接口函數(shù),Objective-C的分類概念和這個很相似,甚至可以說是同一類型的東西,雖然不知道他們誰先誰后出現(xiàn),這個東西的引入,能使得編程方面更加豐富高效。
Objective-C提供了一種與眾不同的方式——Category,可以動態(tài)的為已經(jīng)存在的類添加新的行為。這樣可以保證類的原始設(shè)計規(guī)模較小,功能增加時再逐步擴(kuò)展。使用Category對類進(jìn)行擴(kuò)展時,不需要訪問其源代碼,也不需要創(chuàng)建子類。Category使用簡單的方式,實(shí)現(xiàn)了類的相關(guān)方法的模塊化,把不同的類方法分配到不同的分類文件中。不過Category并不能給類擴(kuò)展出屬性,這點(diǎn)要注意,因為Object C不支持這樣的屬性擴(kuò)展。
分類(Category)的定義語法如下所示。
@interface ClassName (CategoryName)
@end
這里好像它們還有一個約定俗成的習(xí)慣,將聲明文件和實(shí)現(xiàn)文件名稱統(tǒng)一采用“原類名+Category”的方式命名。所以O(shè)C的這種功能雖然和C#功能差不多,但是這點(diǎn)約定和C#不一樣,C#不管你放到哪里都行,但是我們還是會應(yīng)該尊重它的規(guī)則。
例如,我們給XYZPerson類增加一個擴(kuò)展方法的定義如下所示,這個定義的函數(shù)約定是放到文件"XYZPerson+XYZPersonNameDisplayAdditions.h"里面。
#import "XYZPerson.h"
@interface XYZPerson (XYZPersonNameDisplayAdditions)
- (NSString *)testMethod;
@end
那么它的實(shí)現(xiàn)代碼如下所示,它的代碼約定是放到 "XYZPerson+XYZPersonNameDisplayAdditions.m"里面。
#import "XYZPerson+XYZPersonNameDisplayAdditions.h"
@implementation XYZPerson (XYZPersonNameDisplayAdditions)
- (NSString *)testMethod {
return [NSString stringWithFormat:@"%@, %@", self.lastName, self.firstName];
}
@end
在C#里面,擴(kuò)展方法是命名空間相關(guān)的,一旦跳出了命名空間的范圍,這個擴(kuò)展函數(shù)就不在起作用,而Objective-C的這個和類一樣,沒有命名空間的概念,因此在擴(kuò)展的時候,需要小心謹(jǐn)慎一點(diǎn),否則容易導(dǎo)致分類的接口和類本身發(fā)生沖突。基于這個原因,所以蘋果建議也是給分類的接口增加一個前綴,命名則采用接口的一貫規(guī)則,如下面代碼所示。
@interface NSSortDescriptor (XYZAdditions)
+ (id)xyz_sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending;
@end
這樣擴(kuò)展方法名稱雖然長了一點(diǎn),但是基本上確保和普通的接口方法不會發(fā)生沖突了。
Category的使用場景:
1、當(dāng)你在定義類的時候,在某些情況下(例如需求變更),你可能想要為其中的某個或幾個類中添加方法。
2、一個類中包含了許多不同的方法需要實(shí)現(xiàn),而這些方法需要不同團(tuán)隊的成員實(shí)現(xiàn)
3、當(dāng)你在使用基礎(chǔ)類庫中的類時,你可能希望這些類實(shí)現(xiàn)一些你需要的方法。
遇到以上這些需求,Category可以幫助你解決問題。當(dāng)然,使用Category也有些問題需要注意,
1、Category可以訪問原始類的實(shí)例變量,但不能添加變量,如果想添加變量,可以考慮通過繼承創(chuàng)建子類。
2、Category可以重載原始類的方法,但不推薦這么做,這么做的后果是你再也不能訪問原來的方法。如果確實(shí)要重載,正確的選擇是創(chuàng)建子類。
3、和普通接口有所區(qū)別的是,在分類的實(shí)現(xiàn)文件中可以不必實(shí)現(xiàn)所有聲明的方法,只要你不去調(diào)用它。
還有一種成為類擴(kuò)展的功能,它是針對存在代碼的類的情況,也就是你的類代碼和你擴(kuò)展的源碼是同時編譯的情況下。
類擴(kuò)展的方法和上面的分類類似,他們不需要寫擴(kuò)展分類的名稱,這個有點(diǎn)像匿名擴(kuò)展分類的概念了,如下所示
@interface ClassName ()
@end
這個匿名的擴(kuò)展分類,和普通的Category不同,它除了可以方法外,還可以添加屬性或者變量的。
2、協(xié)議Protocal
這個概念有很大程度上和C#的接口類似,但是它有所不同,它可以可選的實(shí)現(xiàn)接口@optional,也有必選的實(shí)現(xiàn)接口@required,雖然Objective-C里面已經(jīng)有一個關(guān)鍵字 @interface,不過這個和Protocal還是有不同的。
和C#的接口一樣,這種協(xié)議也可以繼承自另外一個Protocal,也就是他們可以有繼承關(guān)系。
@protocol NewProtocal <Protocal>
@end
由于Objective-C開發(fā)的很多應(yīng)用,如IOS的應(yīng)用,他們在MVC的開發(fā)模型里面,都大量使用了代理模式,這種Protocal很好的處理了這種關(guān)系。在iOS和OS X開發(fā)中,Apple采用了大量的代理模式來實(shí)現(xiàn)MVC中View和Controller的解耦。
例如UIView產(chǎn)生的所有事件,都是通過委托的方式交給Controller完成。根據(jù)約定,框架中后綴為Delegate的都是Protocol,例如UIApplicationDelegate,UIWebViewDelegate等。
在C#里面有很多如IClonable, IEnumerable這樣的接口,只要實(shí)現(xiàn)了,就能實(shí)現(xiàn)克隆和枚舉,在Objective-C里面,這個就是可以使用Protocal來替代了,如果某個協(xié)議繼承了NSObject,那么這是代表在此聲明的協(xié)議,是NSObject協(xié)議的衍生協(xié)議(不是NSObject類),也就是說,這里的語境理解NSObject是一個協(xié)議,如果是在@Interface里面的繼承關(guān)系,那么那個就是NSObject對象。有點(diǎn)意思哦。
下面是一個可選和必選的協(xié)議定義例子。
@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
@optional
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
- (BOOL)shouldExplodeSegmentAtIndex:(NSUInteger)segmentIndex;
@required
- (UIColor *)colorForSegmentAtIndex:(NSUInteger)segmentIndex;
@end
由于協(xié)議有可選和必選,如果我們想知道某個動態(tài)的對象是否具有某個接口函數(shù),就是通過@selector操作符來進(jìn)行判斷的。
NSString *thisSegmentTitle;
if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
}
和C#的接口定義類似,Objective-C的一個類對象它可以實(shí)現(xiàn)多的協(xié)議,如下例子是一個類的接口定義實(shí)現(xiàn)幾個協(xié)議的情況。
@interface MyClass : NSObject <MyProtocol, AnotherProtocol, YetAnotherProtocol>
...
@end
這樣就實(shí)現(xiàn)了MyClass對象只有一個基類對象,但是可以實(shí)現(xiàn)多個協(xié)議(C#是多個接口)的情況。