iOS從入門到精通之 :協議(protocol)
Hello 大家好,我是Stefan,今天給大家帶來的時iOS程式設計中非常重要的一個知識要點:協議
1.1 什麼是協議
1.1.1 協議基本概念
《倚天屠龍記》中,峨眉派的紀曉芙因為愛上了明教光明右使楊逍,違反了峨眉派“不得與魔教人士往來”的條規,最終被滅絕師太一掌斃命,香消玉殞。可惜一位絕世佳人,卻因為條條框框的門規協定而枉送了性命。
iOS程式設計中的協議其實也是如此,iOS裡面協議不是類,它是一種約定,約定了哪些條款一定要你實現,哪些條款你可以自己選擇是不是要實現。而一定要實現的協定就像是峨眉派的條規啦,如果你想學紀曉芙,偷懶不去實現一些協議裡的必須實現的條款,那下場就和她一樣悲情了。但是它當然比迂腐固化的峨眉嚴規要自由許多,畢竟iOS程式設計是現代的產物,也就是因為iOS中的協議提供了可選的條款,這樣你可以有很大的自由度,比如像“不得與魔教人士往來”這樣霸道的條款你可以寫到可選條款裡,這樣你不想遵守的時候就不遵守,反正它不是必須要實現的條款;這就是iOS的協議。
好了,廢話說了挺多,我們來看看iOS裡面到底如何來使用協議。
協議聲明瞭其它類可以呼叫的程式設計介面,這有點類似與java裡的介面,它使得類直接的通訊變的簡單明瞭,下圖清晰的反應了協議與類之間的概念:
圖1 Protocol概述
上圖中我們可以看到,協議(Protocol)將兩個繼承關係很遠的類聯絡起來。
一個普通的協議定義如下:
@protocol ProtocolName |
//這裡宣告一些方法 |
@end |
我們再來看一個餅狀圖的示例:
圖2 餅狀圖
如圖,餅狀圖一般用來顯示資料,但是我們如果針對每一個有不同資料的餅狀圖都寫一個類,那工作量就太大了。一種方法是可以通過餅狀圖的屬性來自定義,當然,iOS裡給我們提供了另一種較為快捷的方法,那就是用協議。
協議裡面提供了可以提供一系列方法來供我們自定義餅狀圖,我們稱這些協議為資料來源協議,如下是上面提到的餅狀圖可能的資料來源協議:
@protocol XYZPieChartViewDataSource//協議名稱 |
- (NSUInteger)numberOfSegments;//餅狀圖的段數 |
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;//特定段 所佔的百分比 |
@optional//可選擇性實現的方法 |
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex; 的標題 |
@end |
協議已經定義了,那麼如何來通過協議自定義我們餅狀圖檢視呢?我們需要在餅狀圖檢視的標頭檔案中加入一個屬性,通過這個屬性來與資料來源建立聯絡,由於資料來源可以是任何的類(只要這個類遵守相關資料來源協議),所以屬性的型別應該是id,後面還可以指定具體的協議名稱,程式碼如下:
@interface XYZPieChartView : UIView//餅狀檢視,繼承自UIView |
@property (weak) id <XYZPieChartViewDataSource> dataSource;//資料來源屬性 的型別是id <XYZPieChartViewDataSource>,其中XYZPieChartViewDataSource標定 了這個資料來源遵守的協議 |
... |
@end |
注意:資料來源屬性和代理屬性一般需要使用weak來標示屬性,原因在於避免迴圈引用。
1.1.2協議的方法
協議預設宣告在其中的方法為必須實現的方法。也就是說只要遵守了這個協議,那麼這些方法必須要去實現。
但是前面我們也提到了,iOS畢竟是先進社會的產物,它更加的人性化,因此,它還提供了可選的方法,我們可以在只有我們需要的時候才去實現它,這樣靈活性就很高了。
例如,前面的餅狀圖示例中,我們如果實現了titleForSegmentAtIndex方法,那麼將會顯示標題,反之則沒有,它就是一個可選的方法。
通過@optional標誌我們可以標識可選方法,程式碼如下:
@protocol XYZPieChartViewDataSource |
- (NSUInteger)numberOfSegments; |
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex; |
@optional//可選方法標誌 |
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;//可選方法 |
@end |
@optional標誌下所有的方法都應是可選的方法,除非下面又有其它的標誌,比如如果下面出現了@required標誌的話,那麼從@required開始再下面的方法就不是可選的方法了,而是必須實現的方法。程式碼示例如下:
@protocol XYZPieChartViewDataSource |
- (NSUInteger)numberOfSegments; |
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex; |
@optional//可選的方法標誌,直到@required標誌,都是可選的方法 |
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex; |
- (BOOL)shouldExplodeSegmentAtIndex:(NSUInteger)segmentIndex; |
@required//必須的方法標誌,以下都是必須的方法 |
- (UIColor *)colorForSegmentAtIndex:(NSUInteger)segmentIndex; |
@end |
上面的示例中定義一個有著三個必須實現的方法和兩個可選擇實現的方法的協議。
1.1.3 避免不遵守協議的風險
前面提到了紀曉芙因為沒有遵守峨眉派的門規,或者說協定而命喪滅絕之手,假使她能提前知道這個門規必須遵守,或者有人提醒她不遵守的嚴重後果,她可能就會為了楊不悔而遠走他鄉了。當然這都是後話,不過強大先進的iOS考慮到了這點,為了避免悲劇的發生。
當我們需要呼叫協議裡面的可選方法時,我們不知道遵循協議的類是不是已經實現了這些方法,這時我們可以通過respondsToSelector 方法來判斷是否實現了某個方法,程式碼示例如下:
NSString *thisSegmentTitle;//段標題 |
if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {//判斷是否存在 titleForSegmentAtIndex方法 |
thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];//呼叫方法 |
} |
1.1.4 協議的繼承機制
就像其它Objective-C類可以繼承一樣,協議也有類似的機制,我們可以使得一個協議遵循另一個協議。
如果一個協議遵循另一個協議,類似與繼承機制,你就需要在協議中提供遵循的協議的方法,一般的,我們在iOS裡寫協議都回遵循NSObject協議。不過由於一般我們都是使用NSObject的子類,所以我們不需要提供NSObject協議方法的實現,對於遵循協議的形式,示例如下:
@protocol MyProtocol <NSObject> |
... |
@end |
在上例中,任何遵循了MyProtocol的協議也會自動的遵循NSObject裡面宣告的方法。
1.1.5 如何遵循協議
為了表明一個類遵循相關的協議,我們使用尖括號來包含協議,示例程式碼如下:
@interface MyClass : NSObject <MyProtocol> |
... |
@end |
一個類的例項如果遵循了相應的協議的話,那它就不僅僅是實現它本身在標頭檔案裡宣告的方法了,他還要實現協議裡宣告的方法,當然,他不需要在自己的標頭檔案裡再次宣告,只需要實現就可以了。
當然,有時候我們覺得一個協議太少了,這個時候是不是會考慮用多個協議呢?iOS裡面我們可以通過逗號吧多個協議隔開,來實現同時遵循多個協議,示例程式碼如下:
@interface MyClass : NSObject <MyProtocol, AnotherProtocol, YetAnotherProtocol> |
... |
@end |