Protocol in Objective-C

前面提到了 OOP 的繼承,但不像 C++可以有多重繼承,Objective-C 是單一繼承的,如果想要做到一個類別同時擁有多種型別的能力,可以透過實作其它型別的 interface 來達成這個目的。在 Java/AS3 是用”interface”這個關鍵字,在 Objective-C 則是用”@protocol”。(有寫過 Java/AS3 的要特別注意不要把 interface 跟 protocol 搞混了,在 Objective-C 的 interface 等於 Java/AS3 的 class,而 protocol 則是相當於 interface)

直接來看看要怎麼做吧。如果你要新增一個自定的 protocol 的話,可以直接在你的專案裡新增一個 protocol 檔:

當然,你要全部寫在一起也沒人反對,只是為了模組化以及以後的可重複使用考量,建議獨立出來另外寫。新增完成之後(它是一個 header 檔),就可以開始來寫了,程式碼如下:

@protocol Drawable

-(void) draw;
-(void) changeColor;

@end

在 Objective-C 裡的 protocol 是用 @protocol 這個語法來定義的。在上面這段程式碼裡,我放了兩個方法,但沒有寫內容。接下來如果我要實作自這個 protocol 的話,所有定義在@protocol 裡的方法都得實作出來。另外,在 Objective-C 2.0 之後加了 @required@optional 的語法,可以讓你設定這個 method 是不是必需一定要實作的項目。用法如下:

@protocol Drawable

@required
-(void) draw;
-(void) changeColor;

@optional
-(void) whateverMethod;

@end

如果沒特別標明的,預設是 @required。如果你要實作這個 protocol 的話,照英文字面來看,@required 的部份是規定要實作的,@optional 的話就隨你高興了。要注意的是 @required 跟 @optional 這兩個語法的影響範圍,是從它以下所有的 method 都會被影響,直到另一個 directive 或是 @end 為止,所以如果你要省略 @required 的話,記得那些 method 要寫在@optional 前面。接下來來看看要怎麼實作這個 protocol:

#import <Cocoa/Cocoa.h>
#import "Drawable.h"

@interface Book : NSObject <Drawable> {
    int price;
}
@property int price;

@end

實作 protocol 的方法就是用 <> 標記,裡面放 protocol 的名稱。並不限定只能實作一個 protocol,如果要實作多個 protocol 的話,則是用逗點分開:

@interface Book : NSObject <Drawable, Openable>

因為到目前為止,我們都還沒實作那個 protocol 裡定義的方法,所以這時候如果直接按下 Build 的話,就會跳出警告訊息:

接著來把該做的填一填吧。因為在 protocol 的地方已經有定義好了方法,所以在@interface 的地方就不用再特別寫一次,只要在@implementation 裡補上該實作的方法就行了。

// --------------
// interface
// --------------
#import <Cocoa/Cocoa.h>
#import "Drawable.h"

@interface Book : NSObject <Drawable> {
    int price;
}
@property int price;

@end

// --------------
// implementation
// --------------
#import "Book.h"
@implementation Book

@synthesize price;

// 實作方法 draw
-(void) draw {
    NSLog(@"draw me!");
}

// 實作方法 changeColor
-(void) changeColor {
    NSLog(@"change color!");
}

@end

如果你實作了所有 @required 的方法的話,則稱為遵守(conform)或採納(adopt)這個 protocol(硬翻成中文還是覺得怪怪的,還是英文比較簡潔直接)。若要檢查某物件是否有乖乖遵守某個 protocol 的規定:

Book *book = [[Book alloc] init];
if ([book conformsToProtocol:@protocol(Drawable)] == YES) {
    NSLog(@"the book is conform to Drawable protocol");
}

protocol 本身也可以像一般類別的繼承,例如:

@protocol A
-(void) methodA;
@end

@protocol B <A>
-(void) methodB;
@end

這時如果你要實作 protocol B,則 methodA 跟 methodB 都需要實作。

另外,你也可以把 protocol 拿來當一般的型別定義來用,例如:

id <Drawable> some_object;

表示說這個 some_object 是個有實作 Drawable 這個 protocol 的物件,在編譯階段就可以先做型別檢查。當然也可以一次多個,一樣用逗點分開:

id <Drawable, Openable> some_object;

上面提到的這種用 @protocol 來定義方法的,稱做 formal protocol,從名字看大概猜得出來一定也有叫做 informal protocol 的東西,不過這個會在category的部份再做說明。