版權聲明:原創作品,謝絕轉載!否則将追究法律責任。
在現實生活中,當處理某一情況的時候人們往往遵循嚴格的程式。執法人員他們在打官司的收集證據和詢問的時候一定要遵守協定。
在面向對象的語言中,最重要的是一個對象需要定義一些行為在某種情況下。例如:一個tableView希望能夠和資料源互動目的是為了表明他要展示什麼資料。這意味着資料必須回應tableView一組特殊的消息。
這個資料源可能是一些類的執行個體,例如一個Controller或者一個專門繼承NSObject類的資料源類為了讓tableView知道這個對象适不适合做這個資料源,是以必須聲明的是讓這個對象實作必須的方法。
Objective-c允許你定義協定,表明這個方法希望用在特殊的情況。這章的主要内容描述了定義一個标準協定的文法,并且聲明一個類接口來遵守這個協定,這意味着這個類必須實作他需要實作的方法。
協定定義消息傳遞的規則:
一個類的接口聲明了和這個類關聯的屬性和方法。協定和之前定義類接口相比,被用來聲明方法和屬性是為了一些特定的類。
聲明協定的基本文法:
@protocol ProtocolName
// list of methods and properties
@end
協定可以包含類方法執行個體方法和屬性的聲明:
例如一個自定義View類用來展示餅狀圖如下圖:

為了使這個View可能的重複利用,所有的決策資訊應該留給另一個對象,一個資料源。這意味着多個執行個體相同的視圖類,可能展示不同的資訊通過和不同的資料源進行互動。
這個最低的資訊需要包括這個餅圖包含幾個部分,每個部分的相對大小,并且每個部分的标題。是以這個餅圖的協定方法大概包含以下幾個不分:
@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
注意:這個協定用無符号整形來定義标量值,後面章節會詳細讨論。
這個餅圖視圖類将要需要一個屬性來跟蹤資料源對象。這個對象可以是任何類,是以這個屬性的類型是id類型。唯一知道這個對象的資訊是他符合相關的協定。
這個在View裡面定義資料源的屬性像這樣:
@interface XYZPieChartView : UIView
@property (weak) id <XYZPieChartViewDataSource> dataSource;
...
Objective-c用尖括号表示符合這個協定。這個例子聲明了一個弱引用類型通用類型的對象指針并且符合XYZPieChartViewDataSource。
注意:代理和資料源的屬性被聲明弱引用類型的原因是避免強引用的循環。
通過給屬性指定相關的協定,你将要得到一個警告如果你設定的這個對象的屬性不符合這個協定,盡管這個屬性的類型是通用類型的。不要擔心這個執行個體對象是UIViewController還是NSObject。最重要的是他符合這個協定,意味着這個餅圖知道他可以請求需要的資料。
協定可以有可選的方法:
預設情況下協定聲明下的方法都是必須實作的方法。這意味着任何類遵守這個協定必須實作他的這些方法。
你也可以聲明可選的方法在協定裡面。這些方法是某些類選擇實作的。
例如你可以決定這個顯示餅圖示題的這個協定方法是可選的。如果這個資料源對象不實作這個可選的方法。那麼就沒有标題顯示在餅圖上。我們可以如下聲明可選的協定方法:
@optional
在這個例子中隻有後面這個方法是可選的。之前的那些方法沒有說明是以預設還是必須實作的。
這個@optional指令适合任何方法遵循他,要麼直到最後協定的定義,或者到另一個指令的聲明例如@required例如:
- (BOOL)shouldExplodeSegmentAtIndex:(NSUInteger)segmentIndex;
@required
- (UIColor *)colorForSegmentAtIndex:(NSUInteger)segmentIndex;
這個例子定義了三個必須實作的方法還有兩個可選的方法。
確定可選方法是在運作時被實作的:
如果這個協定方法是可選的你必須檢查這個對象是否實作了這個方法再調用他。例如:這個餅圖可能這樣測試這個方法:
NSString *thisSegmentTitle;
if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
}
這個respondsToSelector:方法使用一個選擇器,他指的是一個方法編譯後的辨別符,你能提供正确的辨別符通過使用@selector()指令和指定的方法名稱。
如果這個資料源在這個例子中實作這個方法,這個标題就出現,否則這個标題是空的。
注意:局部變量預設初始化為nil。
如果你試圖調用了未定義在特殊協定裡面的方法,那麼你就會得到一個惡錯誤。為了避免這個編譯錯誤你需要設定你的自定義協定采用NSObject協定。
協定是可以繼承其他的協定的:
就像一個Objective-c類可以繼承其他的父類一樣,你可以指定一個協定符合另一個協定。
例如一個最好的實踐定義你的協定符合NSObject協定(一些NSObject行為從其類接口中分離到一個單獨的協定檔案裡)NSObject類是采用NSObject協定的。
通過表明你自己的協定符合NSObject的協定來表明任何對象采用自定義協定還将提供NSObject協定的方法。因為你可能用NSObject的一些子類,你不要為這些NSObject協定方法提供實作而擔心。該協定提供是有用的就像上面描述的那樣。
指定一個協定符合另一個協定,你需要在尖括号裡提供另一個協定的名字就像這樣:
@protocol MyProtocol <NSObject>
在這個例子中任何采用自定義協定同樣也能使用NSObject協定的方法。
符合協定:
聲明一個類符合協定需要在尖括号裡寫上協定的名字像這樣:
@interface MyClass : NSObject <MyProtocol>
這意味着MyClass的執行個體不僅僅能響應自己在接口定義的方法,而且這個MyClass也提供在myProtocol必選的方法的實作。沒有必要重新聲明這些方法在接口檔案裡,采用協定是足夠的。
如果你需要一個類采用多個協定,你可以指定他們作為一個以逗号分隔的清單像這樣:
@interface MyClass : NSObject <MyProtocol, AnotherProtocol, YetAnotherProtocol>
技巧:如果發現你自己符合了很多協定在類裡面,這可能意味着你可能需要重構那些過去複雜的類,分裂了必須的行為在很多小類,每個都有清晰的責任。
一個相對新的OSX作業系統普遍存在的缺陷和IOS開發是使用一個應用程式委托類包含大多數應用程式的功能(管理底層資料結構,服務資料到多個使用者界面元素,以及響應手勢和其他使用者互動)。随着複雜性的增加,類變的更難維護。
一旦你已經聲明符合某個協定,這個類必須最少提供協定要必須實作方法的實作,以及任何你選的可選方法,這個編譯器将要警告你如果你沒有實作必須實作的方法。
注意:這個定義在協定裡面的方法就像定義在接口裡面的方法一樣。這個方法的名字和參數類型在實作的時候必須和在協定裡面定義的一樣。
cocoa和cocoa touch定義了大量的協定
協定被cocoa和cocoatouch對象在很多不同的環境中使用。例如,tableView類即用資料源對象來提供他需要的資訊。也定義了自己的資料源協定。tableView的類允許你設定委托對象,必須符合UITableViewDelegate協定,這個代理的責任負責處理使用者互動或者定制顯示的某些條目。
一些協定用于表示類之間的相似性。而不是與特定類的需求,一些協定相反與更普遍的cocoa或者cocoa touch溝通聯系機制,可以采用多個不相關的類。
例如一些架構資料對象像集合對象NSArray和NSDictionay支援NSCoding協定,這意味着他可以編解碼給他們的屬性為了存檔或者釋出作為原始資料。NSCoding使把對象寫入磁盤變的更容易,這些對象符合這個協定。
一些Objective-c的低級特性也依靠協定。為了使用快速枚舉,例如,一個集合必須符合NSFastEnumeration協定,就像在Fast Enumeration Makes it East to Enumerate a Collection。此外,一些對象可以被copy,例如當用屬性的copy關鍵字的時候,這些對象必須符合NSCopying協定,否則你将要在運作時得到一個異常。
協定都是匿名的
協定也可以在對象不确定的情況使用,或者需要隐藏。
例如一個架構的開發者可能選擇不公布某些類的接口。因為這些類名字不知道,用這個架構的人不可能直接建立這個類的執行個體。相反的一些在架構的對象被典型的設計為傳回一個建立好的執行個體對象。像這樣:
id utility = [frameworkObject anonymousUtility];
為了使anonymousUtility對象更有用,這個架構開發者公布了協定的某些方法。盡管原始類的接口沒有被提供。這意味着類保持匿名,對象仍然可以被使用在一個有限的方式:
id <XYZFrameworkUtility> utility = [frameworkObject anonymousUtility];
如果你寫IOS的應用用core Data架構,例如你可能會遇到NSFetchedResultsController類。這個類幫助datasource對象為tableView提供存儲資料,使他更容易提供行數的資訊。
如果你能用到tableView的多個分區,你也可以得到一些分區資訊的結果。而不是傳回一個特定的類包含分區資訊,NSFetchedResultsController傳回一個匿名對象,這個匿名對象符合NSFetchedResultsSectionInfo協定。這意味着他仍然可以查詢對象為你需要的資訊,比如一個分區多少行:
NSInteger sectionNumber = ...
id <NSFetchedResultsSectionInfo> sectionInfo =
[self.fetchedResultsController.sections objectAtIndex:sectionNumber];
NSInteger numberOfRowsInSection = [sectionInfo numberOfObjects];
盡管你不知道sectionInfo 對象,NSFetchedResultsSectionInfo 協定表明他仍然可以響應numberOfObjects方法。