前面提到了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檔),就可以開始來寫了,程式碼如下:
1 2 3 4 5 6 | |
在Objective-C裡的protocol是用@protocol
這個語法來定義的。在上面這段程式碼裡,我放了兩個方法,但沒有寫內容。接下來如果我要實作自這個protocol的話,所有定義在@protocol裡的方法都得實作出來。另外,在Objective-C 2.0之後加了@required
跟@optional
的語法,可以讓你設定這個method是不是必需一定要實作的項目。用法如下:
1 2 3 4 5 6 7 8 9 10 | |
如果沒特別標明的,預設是@required。如果你要實作這個protocol的話,照英文字面來看,@required的部份是規定要實作的,@optional的話就隨你高興了。要注意的是@required跟@optional這兩個語法的影響範圍,是從它以下所有的method都會被影響,直到另一個directive或是@end為止,所以如果你要省略@required的話,記得那些method要寫在@optional前面。接下來來看看要怎麼實作這個protocol:
1 2 3 4 5 6 7 8 9 10 | |
實作protocol的方法就是用”<>”標記,裡面放protocol的名稱。並不限定只能實作一個protocol,如果要實作多個protocol的話,則是用逗點分開:
1
| |
因為到目前為止,我們都還沒實作那個protocol裡定義的方法,所以這時候如果直接按下Build的話,就會跳出警告訊息:
接著來把該做的填一填吧。因為在protocol的地方已經有定義好了方法,所以在@interface的地方就不用再特別寫一次,只要在@implementation裡補上該實作的方法就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | |
如果你實作了所有@required的方法的話,則稱為遵守(conform)或採納(adopt)這個protocol(硬翻成中文還是覺得怪怪的,還是英文比較簡潔直接)。若要檢查某物件是否有乖乖遵守某個protocol的規定:
1 2 3 4 5 | |
protocol本身也可以像一般類別的繼承,例如:
1 2 3 4 5 6 7 | |
這時如果你要實作protocol B,則methodA跟methodB都需要實作。
另外,你也可以把protocol拿來當一般的型別定義來用,例如:
1
| |
表示說這個some_object是個有實作Drawable這個protocol的物件,在編譯階段就可以先做型別檢查。當然也可以一次多個,一樣用逗點分開:
1
| |
上面提到的這種用@protocol來定義方法的,稱做formal protocol
我們知道所有的自訂的Class 都要繼承自 NSObject 這個 Class ,而在 protocol 的世界裡也有一個叫NSObject 的 protocol ,如果去查這個 protocol 所規範的 method 不外乎就是 init,release ,retain 等等的在NSObject 有定義過的 method 。其實 NSObject就是遵循了<nsobject> 而實作了重要的 method。如果我們這樣寫。@protocolOmniprinter<nsobject>代表著這個Omniprinter protocol 也採用了<nsobject>這個 protocol。那也就是說如果這樣寫。id<omniprinter> delegate;也就代表著 delegate 也要實作 <nsobject> 所宣告的@required method。那是不是要寫很多程式?不用怕,還記得所有的我們自訂的或是系統提供的Class都繼承自NSObject 這個 Class 嗎?自然也就有 <nsobject>的實作了,只要我們照著規範做就可以省去很多事情。
Protocol 的其它问题
1. 使用时为什么要加上 .delegate = self
物件名称.delegate = self,是在採用任何协定时 一定会看到的一行程式码,由于定义协定的类别并不需要实作协定内的方法,因为实作的部份是由採纳协定的类别来实作,但是它又必须要知道是由哪一个类别来实作,因此我们必须要把採纳协定类别的 instance 交给定义协定的类别,让它来使用。
另一方面并不是任何类别都可以将 instance 传给定义协定的类别来使用,其原因是,我们在定义此协定的类别里有宣告 delegate 变数时,有限定它必须要採纳此协定(id delegate)如果没有採用该协定就将 instance 传给定义该协定的类别,Xcode 同样会发出警告讯息。
2. 为什么协定的生效位置不能写在建构式中
协定的生效位置写在建构式中,并不会造成程式编译上的任何问题,因为这是属于逻辑上的错误,协定要正常生效它必须要知道实作它方法的类别的 instance,如果将生效的位置写在建构式中,在建立定义此协定的形态的变物件时,它的确会去触发此协定内的方法,但是由于并没有给它实作此协定方法类别的 instance,因此不会有任何效果产生,反之,如果一定要将生效的位置写在建构式中,那么在初始化时就必须要设定好 delegate 才行,也就是使用初始化的方法函式里还必须要带入一个参数物件好指定给 delegate。
3. 在定义协定时同时也可以採用其他的协定
如果在定义协定时同时又採用其他的协定,这会导致之后採纳此协定的类别,它必须同时实作出两个协定内的方法,同样地,你也可以利用此方式来扩充那些已经存在的协定。
- @protocol FurnaceDelegate <其它可能的协定名称>
4. 使用 @optional 提供选择性的实作
@optional,如同它字面上的意义,在 @optional 之后的方法都可以是选择性的实作,在定义协定时使用此方法,可以让之后採纳此协定的类别不一定要完全实作出协定内的所有方法。
- @protocol FurnaceDelegate
- - (void)whenCalledDelegeteFunction;
- @optional
- -(void)optionalDelegeteFunction;
- @end