鍍金池/ 教程/ iOS/ 使用協(xié)議 - Working with Protocols
使用對(duì)象 - Working with Objects
使用塊 - Working with Blocks
自定義現(xiàn)有的類 - Customizing Existing Classes
使用協(xié)議 - Working with Protocols
定義類 - Defining Classes
賦值與集合 - Values and Collections
關(guān)于 Objective-C
命名規(guī)則 - Conventions
錯(cuò)誤處理 - Dealing with Errors
數(shù)據(jù)封裝 - Encapsulating Data

使用協(xié)議 - Working with Protocols

在現(xiàn)實(shí)世界中,公務(wù)人員在處理某些情況時(shí)往往需要遵循嚴(yán)格的程序。例如,執(zhí)法人員在進(jìn)行詢問或收集證據(jù)時(shí)要“遵循協(xié)議”。

在面向?qū)ο缶幊痰氖澜?,重要的是在一個(gè)給定的情況下能夠定義一組對(duì)象的行為。舉個(gè)例子,一個(gè)表視圖為了查明它需要顯示什么內(nèi)容,希望能夠與一個(gè)數(shù)據(jù)源對(duì)象通信。這意味著數(shù)據(jù)源必須響應(yīng)表視圖可能發(fā)送的一組特定的消息。

數(shù)據(jù)源可以是任何類的一個(gè)實(shí)例,比如視圖控制器(子類 NSViewController OS X 或 UIViewController iOS)或者是一個(gè)剛從 NSObject 繼承的專用的數(shù)據(jù)源類。為了使表視圖知道一個(gè)對(duì)象是否為合適的數(shù)據(jù)源,重要的是能夠聲明對(duì)象實(shí)現(xiàn)的必要方法。

Objective-C 允許您定義協(xié)議,聲明的方法將被用于一個(gè)特定的情況。本章介紹了定義一個(gè)正式協(xié)議的語(yǔ)法,并解釋了如何標(biāo)記一個(gè)類界面使其符合一個(gè)協(xié)議,這意味著該類必須執(zhí)行要求的方法。

協(xié)議定義消息傳遞合同

一個(gè)類的接口聲明這個(gè)類的方法和屬性。相比之下,一個(gè)協(xié)議聲明的方法和屬性,獨(dú)立于任何特定的類。

定義一個(gè)協(xié)議的基本語(yǔ)法如下:

@protocol ProtocolName
// list of methods and properties
@end

協(xié)議可以包括聲明實(shí)例方法和類方法,以及屬性。

作為一個(gè)例子,考慮一個(gè)定制的視圖類,用于顯示一個(gè)餅圖,如圖 5-1 所示。

圖 5-1 餅圖自定義視圖

http://wiki.jikexueyuan.com/project/programming-with-objective-c/images/piechartsim.png" alt="image" />

為了使視圖盡可能重用,所有的決策信息應(yīng)該留給另一個(gè)作為數(shù)據(jù)源的對(duì)象。這意味著相同的視圖類的多個(gè)實(shí)例可以顯示不同的信息僅僅通過與不同數(shù)據(jù)源的交流。

餅圖視圖所需的最小信息包括分塊的數(shù)量,每個(gè)分塊的相對(duì)大小,每個(gè)分塊的標(biāo)題。餅圖的數(shù)據(jù)源協(xié)議,因此,看起來像這樣:

@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@end

注:本協(xié)議使用無(wú)符號(hào)整數(shù)的 NSUInteger 值標(biāo)量值。這種類型在下一章詳細(xì)討論。餅圖視圖類接口需要有一個(gè)記錄數(shù)據(jù)源對(duì)象的屬性。這個(gè)對(duì)象可以是任何類,所以基本屬性類型為 id 。關(guān)于對(duì)象唯一知道的是其符合相關(guān)協(xié)議。

聲明數(shù)據(jù)源屬性視圖的語(yǔ)法應(yīng)該像這樣:

@interface XYZPieChartView : UIView
@property (weak) id <XYZPieChartViewDataSource> dataSource;
...
@end

Objective-C 使用尖括號(hào)來表示與協(xié)議的一致性。本例為一個(gè)通用類對(duì)象指針聲明一個(gè)弱屬性符合 theXYZPieChartViewDataSource 協(xié)議。

注:接口和數(shù)據(jù)源屬性通常標(biāo)記為弱,根據(jù)前面提到的對(duì)象圖管理原因,避免強(qiáng)引用周期。

指定與所需協(xié)議一致的屬性,如果您試圖將屬性設(shè)置為一個(gè)對(duì)象, 這是不符合協(xié)議的,你會(huì)得到一個(gè)編譯器警告,盡管基本屬性類的類型是通用的。對(duì)象是否為 UIViewController orNSObject 的一個(gè)實(shí)例是無(wú)關(guān)緊要的。最重要的是,它符合協(xié)議,這意味著餅圖視圖知道它可以請(qǐng)求所需要的信息。

協(xié)議有可選擇的方法

默認(rèn)情況下,在一個(gè)協(xié)議中聲明的所有方法都是需要的方法。這意味著符合協(xié)議的任何類必須實(shí)現(xiàn)這些方法。

在協(xié)議里指定可選方法也是可能的。這些方法是一個(gè)類只要需要就可執(zhí)行的。

例如,您可能會(huì)決定,餅圖的標(biāo)題應(yīng)該是可選的。如果數(shù)據(jù)源對(duì)象不實(shí)現(xiàn) titleForSegmentAtIndex: ,則應(yīng)該沒有標(biāo)題顯示在視圖中。

您可以使用 @optional 指令協(xié)議方法標(biāo)記為可選,如下:

@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
@optional
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@end

本例中,只有 titleForSegmentAtIndex: 方法標(biāo)記為可選。前面的方法沒有指令,所以被認(rèn)為是必需的。

@optional 指令適用于遵循它的任何方法 ,直到協(xié)議定義的最后,或者遇到另一個(gè)指令之前,例如 @required。你可能會(huì)添加進(jì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

這個(gè)例子定義了一個(gè)有三種必需方法和兩種可選方法的協(xié)議。

運(yùn)行時(shí)檢查可選方法的實(shí)現(xiàn)

如果一個(gè)方法在協(xié)議中被標(biāo)記為可選的,您必須檢查是否有對(duì)象在實(shí)現(xiàn)之前試圖調(diào)用它。

例如,餅圖視圖測(cè)試分塊標(biāo)題的方法可能是這樣的:

  NSString *thisSegmentTitle;
      if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
          thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
      }

respondsToSelector:方法使用一個(gè)選擇器,引用編譯后的標(biāo)識(shí)符的方法。您可以使用 @selector() 指令和指定方法的名稱來提供正確的標(biāo)識(shí)符。

如果在本例中數(shù)據(jù)源實(shí)現(xiàn)了這個(gè)方法,那么標(biāo)題被使用;否則,標(biāo)題仍然是零。

切記:本地對(duì)象變量自動(dòng)初始化為零。

如果您試圖調(diào)用有一個(gè)協(xié)議中 id 的 respondsToSelector: 方法,你會(huì)得到一個(gè)沒有已知的實(shí)例方法的編譯錯(cuò)誤。一旦你有了一個(gè)協(xié)議中的 id ,所有靜態(tài)類型檢查復(fù)原,如果你試圖調(diào)用任何未在指定協(xié)議中聲明的方法,系統(tǒng)會(huì)報(bào)錯(cuò)。避免編譯錯(cuò)誤的一種方法是設(shè)置自定義協(xié)議采用 NSObject 協(xié)議。

協(xié)議繼承其他協(xié)議

一個(gè) Objective-C 類可以繼承一個(gè)父類,以同樣的方式,你還可以指定一個(gè)協(xié)議符合另一個(gè)協(xié)議。

作為一個(gè)例子,最佳的實(shí)踐是定義您的協(xié)議符合 NSObject 協(xié)議(一些 NSObject 行為從它們的類接口劃分到一個(gè)單獨(dú)的協(xié)議; NSObject 類采用 NSObject 協(xié)議)。

通過表明自己的協(xié)議符合 NSObject 協(xié)議,這表明任何采用自定義協(xié)議的對(duì)象,還將提供每個(gè) NSObject 協(xié)議方法的實(shí)現(xiàn)。因?yàn)槟憧赡苁褂靡恍?NSObject 的子類,你不需要擔(dān)心為這些 NSObject 提供自己的實(shí)現(xiàn)方法。不管怎樣,對(duì)于前述的情況,采用的協(xié)議是有效的。

指定一個(gè)協(xié)議符合另一個(gè)協(xié)議,您需提供其他協(xié)議的名稱在尖括號(hào)內(nèi),像這樣:

@protocol MyProtocol <NSObject>
...
@end

在這個(gè)例子中,任何采用 MyProtocol 的對(duì)象,也有效地采用了 NSObject 協(xié)議中聲明的所有方法。

符合協(xié)議

表示一個(gè)類采用了協(xié)議需再次使用尖括號(hào)括起來,像這樣:

@interface MyClass : NSObject <MyProtocol>
...
@end

這意味著任何 MyClass 實(shí)例不僅響應(yīng)在接口中特意聲明的方法, MyClass 還在 MyProtocol 中提供了所需方法的實(shí)現(xiàn)。不需要在類的接口重新定義協(xié)議方法,采用協(xié)議就足夠了。

注:編譯器不會(huì)自動(dòng)合成在采用的協(xié)議里聲明的屬性。

如果您需要一個(gè)類采用了多種協(xié)議,你可以用一個(gè)逗號(hào)分隔,像這樣:

@interface MyClass : NSObject <MyProtocol, AnotherProtocol, YetAnotherProtocol>
...
@end

提示:如果您發(fā)現(xiàn)自己在一個(gè)類中采用大量的協(xié)議,它可能是一個(gè)信號(hào),表明您需要重構(gòu)那個(gè)過于復(fù)雜的類,可以通過必要的行為將其拆分到多個(gè)小類,每個(gè)小類都有明確的責(zé)任。

對(duì)新 OS X 和 iOS 開發(fā)者來說,一個(gè)相對(duì)常見的困難是使用一個(gè)應(yīng)用程序委托類去包含一個(gè)應(yīng)用程序的大部分功能(管理底層數(shù)據(jù)結(jié)構(gòu),提供數(shù)據(jù)到多個(gè)用戶界面元素,以及響應(yīng)手勢(shì)和其他用戶交互)。隨著復(fù)雜性的增加,類變得更難以維護(hù)。

一旦您表明遵循某個(gè)協(xié)議,類必須至少為每個(gè)所需的協(xié)議方法提供實(shí)現(xiàn)方法,以及您選擇的任何可選的方法。如果不能實(shí)現(xiàn)任何所需的方法,編譯器會(huì)提醒您。

注:協(xié)議中的方法聲明類似其他任何聲明。協(xié)議中實(shí)現(xiàn)的方法名和參數(shù)類型必須與聲明匹配。

Cocoa和Cocoa Touch定義大量的協(xié)議

Cocoa 和 Cocoa Touch 使用的協(xié)議針對(duì)各種不同情況的對(duì)象。例如,表視圖類( NSTableView OS X and UITableView iOS )都使用一個(gè)數(shù)據(jù)源對(duì)象來為他們提供必要的信息。兩者都定義自己的數(shù)據(jù)源協(xié)議,與上面的例子 XYZPieChartViewDataSource 協(xié)議使用差不多的方法。兩者的表視圖類還允許您設(shè)置一個(gè)委托對(duì)象,又必須符合相關(guān) NSTableViewDelegate 或 UITableViewDelegate 協(xié)議。委托對(duì)象負(fù)責(zé)處理用戶交互,或定制化顯示某些條目。

一些協(xié)議是用于表示類之間沒有相似之處。不是與特定類需求關(guān)聯(lián),一些協(xié)議與更普遍的 Cocoa 和 Cocoa Touch 通信機(jī)制關(guān)聯(lián),可能會(huì)采用多個(gè)不相關(guān)的類。

例如,許多框架模型對(duì)象(如集合類,如 NSArray 和 NSDictionary )支持 NSCoding 協(xié)議,這意味著它們可以編碼和解碼檔案的屬性或分布為原始數(shù)據(jù)。NSCoding 使它相對(duì)容易的將整個(gè)對(duì)象圖寫到磁盤中,提供每個(gè)對(duì)象采用協(xié)議的圖表。

一些 Objective-C 語(yǔ)言級(jí)特性也依靠協(xié)議。為了使用快速枚舉,例如,集合必須采用 NSFastEnumerationprotocol 協(xié)議,如 Fast Enumeration Makes It Easy to Enumerate a Collection 中所述。此外,一些對(duì)象可以被復(fù)制, 例如在使用一個(gè)屬性和一個(gè)復(fù)制屬性時(shí),如 Copy Properties Maintain Their Own Copies 所述。你試圖復(fù)制的任何對(duì)象必須采用 NSCopying 協(xié)議,否則你會(huì)得到一個(gè)運(yùn)行異常。

協(xié)議用于匿名

對(duì)象的類不是已知的,或需要被隱藏的情況下協(xié)議也是很有用的。

比如,一個(gè)框架的開發(fā)人員可能會(huì)選擇不發(fā)布框架內(nèi)一個(gè)類的接口。因?yàn)轭惷Q不清楚,框架的用戶直接創(chuàng)建這個(gè)類的一個(gè)實(shí)例是不可能的。相反,框架內(nèi)一些其他對(duì)象通常會(huì)指定返回一個(gè)現(xiàn)成的實(shí)例,像這樣:

  utility = [frameworkObject anonymousUtility];

為了讓這個(gè) anonymousUtility 對(duì)象是有用的,框架的開發(fā)人員就可以發(fā)布一個(gè)協(xié)議,顯示它的一些方法。但不提供原始類接口,這意味著類保持匿名,對(duì)象仍是在有限的方式內(nèi)被使用:

  id <XYZFrameworkUtility> utility = [frameworkObject anonymousUtility];

如果您在寫一個(gè) iOS 應(yīng)用程序,使用核心數(shù)據(jù)框架,例如,您可能會(huì)遇到 NSFetchedResultsController 類。這個(gè)類是為了幫助一個(gè)數(shù)據(jù)源對(duì)象提供存儲(chǔ)數(shù)據(jù)到一個(gè) iOS UITableView ,便于提供信息的行數(shù)。

如果您正在使用的表視圖內(nèi)容分為多個(gè)部分,您也可以訪問一個(gè)獲取結(jié)果控制器獲取相關(guān)部分信息。而不是返回一個(gè)特定的類包含這部分信息, NSFetchedResultsController 類相反是返回一個(gè)匿名對(duì)象,它符合 NSFetchedResultsSectionInfo 協(xié)議。這意味著它仍然可以查詢你需要的對(duì)象的信息,如一部分內(nèi)的行數(shù):

  NSInteger sectionNumber = ...
      id <NSFetchedResultsSectionInfo> sectionInfo =
              [self.fetchedResultsController.sections objectAtIndex:sectionNumber];
      NSInteger numberOfRowsInSection = [sectionInfo numberOfObjects];

即使您不知道 sectionInfo 對(duì)象的類, NSFetchedResultsSectionInfo 協(xié)議規(guī)定,它可以應(yīng)對(duì) thenumberOfObjects 信息。