Protocol & Delegate

Protocol

在Objective-C 中protocol 就是一個不少 method 的宣告集合的地方,感覺上和interface 有點像而和interface的差別在於protocol 裡面不會有變數的宣告。我們直接來看一個例子。
@protocol Omniprinter

-(void) printInt:(int ) intVar;
-(void) printObj:(NSString *) obj;

@end
這個例子就是,我們宣告了一個 protocol 叫 Omniprinter 前面須要加上關鍵字@protocol,這個protocol 裡面就只有 printInt: 和 printObj: 這兩個 method。Protocol 就這樣簡單地宣告完成了。若是是要接受或叫遵循這個 protocol 的 class就要在@interface這樣寫。
@interface Hello: NSObject <Omniprinter>{
}
@end
這個就表明 Hello 這個 Class 有採用 Omniprinter 所定義的 method 。若是一次要採用不少個 protocol 就用 , 逗點分開每個 protocol 的名字,好比。
@interface Hello: NSObject <Omniprinter, protocol1, protocol2 >
當我們寫在 interface 說要採用某個 protocol 的時候我們就有可能會在 implementation 這樣寫。
@implementation Hello
-(void) printObj:(NSString *) obj{
    NSLog(@"%@", obj);
}
@end
就是把 protocol 所宣告的 method 實做在有採用這個 protocol 的 Class 的 implementation 的部分。在上面的例子筆者故意只寫一個 method 實做,是要來說明 protocol 裡的 method 還有分別的。好比剛剛的 protocol ,Omniprinter 我們加幾個關鍵字。
@protocol Omniprinter

@optional
-(void) printInt:(int ) intVar;
@required
-(void) printObj:(NSString *) obj;

@end
看名字就知道在@optional 之後的 method 是選擇要不要實做的,而在 @required 之後的 method 的是必定要實做的,若是什麼都沒有寫預設就是 @required 。若是不遵這樣的規則Xcode 會給警告。Protocol 本質上就是這樣簡單的一個機制,在 implementation 要實做在 interface 所採用的 protocol 的 method 。

型別裡的Protocol

接著要介紹的觀念是當Protocol 放到型別裡的時候是怎麼樣的情況,先來看這個例子。
id<Omniprinter> delegate ;
這樣子的寫法是表明說 delegate 這個變數,它能夠是任意型別,但必定要遵循 Omniprinter 這個 protocol ,也就是說,delegate 的實體必須要實做 Omniprinter 所定義的@required 的  method 。若是我們限制 delegate 的型別呢?就會這樣寫。
Hello<Omniprinter> * delegate ;
這樣就表明 delegate 這個變數所存的實體必需是 Hello * 這個型別並且要有實做 Omniprinter 這個 protocol 裡面 @required 的 method 。

NSObject 與 <NSObject>

我們知道全部的自訂的Class 都要繼承自 NSObject 這個 Class ,而在 protocol 的世界裡也有一個叫 NSObject 的 protocol ,若是去查這個 protocol 所規範的 method 不外乎就是 init ,release ,retain 等等的在NSObject 有定義過的 method 。其實 NSObject 就是遵循了<NSObject> 而實做了重要的 method 。若是我們這樣寫。
@protocol Omniprinter<NSObject>
表明著這個 Omniprinter protocol 也採用了<NSObject> 這個 protocol 。那也就是說若是這樣寫。
id<Omniprinter> delegate ;
也就表明著 delegate 也要實做 <NSObject> 所宣告的 @required method。那是否是要寫不少程式?不用怕,還記得全部的我們自訂的或是系統提供的Class都繼承自 NSObject 這個 Class 嗎?天然也就有 <NSObject> 的實做了,只要我們照著規範作就能夠省去不少事情。

委任 - Delegate

談到Protocol 就不得不談一個Design Pattern 叫作 Delegate  (委任),在 Objective-C 裡面經經常使用 Protocol 來完成 Delegate 所要作的事情。委任就是某 A 要完成一件事情,A 沒辦法本身獨力完成,而須要借用其餘人,好比 B 的能力來完成。此時,A 就是委任者,B 就是受委任者。換成程式語言來說能力就是 method ,當 A 的能力不足的時候,就是 method 的功能很少,當 A 委任給 B 來作事的時候,就是利用 B 的 method 來完成事情。我們來看一個很簡單的例子。假設 B 是另外一個 Class 。
@interface A {
    B * delegate;
}
@end

@implementation A
-(void) doSomething {
    [ delegate  actionOfB ];
}
@end
這個例子很簡子的只是在 doSomething 這個 method 裡呼叫 delegate 的 actionOfB 這個 method 。但其中隱含的概念是,A 這個 Class 借用 B 的能力 ( method ) 來完成 A 的 doSomething 這件事。delegate 這個變數的型別是 B * ,無論其在什麼時候給一個 B 的實體,在 A 的 method ,doSomething 裡被借用了能力( method ) 就是受了 A 的委任來完成事情。這就是一個很簡單委任的例子。接著我們來看看在 Objective-C 裡怎麼用委任這個概念。尤於要寫的內容太多,這個例子就分紅幾個檔案。把 @interface 都放在 .h 檔,@implementation 都放在 .m  檔裡。
新增 MyWallet.h 以及 MyWallet.m 檔。
在 MyWallet.h 打上這樣的程式碼:
#import <Foundation/Foundation.h>

@protocol examMoney;
@interface MyWallet : NSObject {
    NSString * title;
    float money;
    id<examMoney> delegate;
}
-(void ) showTheMoney;
@property ( assign) id<examMoney> delegate;
@property ( retain) NSString * title;
@property ( assign) float money;
@end

@protocol examMoney<NSObject>

-(void) getWallet:(MyWallet *) wallet withMoney:(float) money;

@end
在 MyWallet.h 裡我們把先寫了這樣的宣告 @protocol examMoney; 這列是要和 compiler 說 examMoney 這串字不是別的正是 protocol 。然後接著宣告 MyWallet 這個 Class 的 interface 的部分。也就是 @interface@end ,這中間的程式碼。MyWallet 這個 Class 有三個實體變數 title , money 和 delegate ,分別都有各自的property 宣告。其中筆者要強調的當然就是 delegate 這個變數,其型別為 id<examMoney> 也就是就說這個變數所要存放的實體必須要實做 <examMoney> 這個 protocol ,雖然看到這列還不知道 protocol 內容長什麼樣,可是就語法來說,這樣就是合法的。再來看一下 delegate 的 property 的 attribute 為 assign ,還記得筆者說過,有 protocol 型別的 property ,其 attribute 要用 assign 就和 money 這個 primitive 型別的變數一樣要用 assign 。在這個例子 NSString * title 的 property attribute 筆者習慣用 retain 。再往下看程式碼,就看到了 <examMoney> 這個 protocol 的內容,真正須要實做的 method 叫什麼名字了。把這個檔案從頭看到尾就發現我們其實把 Class 的宣和 Protocol 的內容放在一塊兒,並且,注意看 Protocol 的 method 第一個傳入的參數是什麼型別?就是這個 Class 的型別。這樣的寫法雖然是 MyWallet 要委任 <examMoney>   給別人完成,但看起來也很像是 MyWallet 把資料傳給另外一個物件。因此也有人說這是一種在 Objective-C 裡面物件之間傳遞資料的方法。至於 showTheMoney 的用途在接下來會解說。
接著看 MyWallet.m 裡面要寫什麼。
#import "MyWallet.h"
@implementation MyWallet
@synthesize delegate, title, money;
-(void) showTheMoney {
    if ([delegate respondsToSelector:@selector(getWallet:withMoney:)]) {
        [delegate getWallet:self withMoney:money];
    }else {
        NSLog(@"Please implement getWallet:withMoney: ");
    }

}
@end
在這個檔案裡,除了把 property ,synthesize 以外,還有寫了 showTheMoney 的實做。showTheMoney 這個 method 實做的內容就是把 delegate 這個有實做 <examMoney> 的物件直接拿來用,也就是呼叫 @selector(getWallet:withMoney:) ,可是在此以前要先檢查一下 delegate 所存的物件是否是真的有實做 getWallet:withMoney: 於是先用 responseToSelector: 來判斷 delegate 是否有實做,否則就印出訊息在 Console。委任者寫完了,再來看看受委任者的寫法。再新增 OmniExamer.h 和 OmniExamer.m 兩個檔案。
OmniExamer.h 的內容會是
#import <Foundation/Foundation.h>
#import "MyWallet.h"

@interface OmniExamer : NSObject<examMoney> {

}

@end
OmniExamer 這個 Class 最主要的目的就是實做 <examMoney> 的 method ,一開始先 #import "MyWallet.h" 讓 OmniExamer 知道 <examMoney> 和 MyWallet 的存在。然後在 Class 最後寫 :NSObject<examMoney> 就是繼承 NSObject 並且遵循 <examMoney>  的規範,就這樣就能夠了。
接著看 OmniExamer.m 的寫法。
#import "OmniExamer.h"

@implementation OmniExamer

-(void) getWallet:(MyWallet *) wallet withMoney:(float) money{
    NSLog(@"The wallet : %@ with Money %f ", wallet.title, money);
}
@end

一開始就是 import OmniExamer.h 然後就是重點,實做 <examMoney> 的 getWallet:withMoney: ,在這個例子就是把 money 印在 Console 並且因為第一個參數就是MyWallet 的物件,因此我們能夠甚至能夠把MyWallet 物件的property 拿來用,就好像這個列子一樣,在印 money 以前把 wallet.title 也印在 Console。
當我們這四個檔案就準備好了,就剩下在 main 測試了。main.m 請以下打字。


#import <Foundation/Foundation.h>
#import "OmniExamer.h"
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    MyWallet * wallet = [MyWallet new];
    wallet.title = @"New Wallet";
    wallet.money = 500.0;
    OmniExamer * oe = [OmniExamer new];
    wallet.delegate = oe;     [wallet showTheMoney];     [pool drain];     return 0; }
一開始當然要先 import OmniExamer.h 這樣才能夠知道 OmniExamer Class 而 OmniExamer.h 又有 import MyWallet.h 因此也能夠知道 MyWallect Class 和 examMoney Protocol 。在 main function 裡就是產生 MyWallet 的實體放在 wallet 這個變數裡,然後設定 title 和 money ,再產生 OmniExamer 的實體放在 oe 。接者就是重點了,把 wallet.delegate = oe ; 這個程式就是表示 wallet.delegate 目前和 oe 指向相同的物件實體,也就是委任關系的成立,wallet 能夠透過 delegate 來使用 oe 所實做 <examMoney> 的 method 。最後就是 [wallet showTheMoney]; 呼叫 showTheMoney ,還記得此 method 用了 delegate 來作事吧,執行到此 delegate 就有 oe 所指的物件實體,也就是 oe 在幫 wallet 作事了,並且 wallet 的資料也傳到 oe 上面。
相關文章
相關標籤/搜索