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

2018-08-12 21:19 更新

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

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

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

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

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

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

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

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

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

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

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

圖 5-1 餅圖自定義視圖

image

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

本例中,只有 titleForSegmentAtIndex: 方法標記為可選。前面的方法沒有指令,所以被認為是必需的。

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

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

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

例如,餅圖視圖測試分塊標題的方法可能是這樣的:

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

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

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

切記:本地對象變量自動初始化為零。

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

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

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

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

通過表明自己的協(xié)議符合 NSObject 協(xié)議,這表明任何采用自定義協(xié)議的對象,還將提供每個 NSObject 協(xié)議方法的實現(xiàn)。因為你可能使用一些 NSObject 的子類,你不需要擔心為這些 NSObject 提供自己的實現(xiàn)方法。不管怎樣,對于前述的情況,采用的協(xié)議是有效的。

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

@protocol MyProtocol <NSObject>
...
@end

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

符合協(xié)議

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

協(xié)議用于匿名

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

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

  utility = [frameworkObject anonymousUtility];

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

  id <XYZFrameworkUtility> utility = [frameworkObject anonymousUtility];

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

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

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

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

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號