摘要:[iOS] 代理委派(一)
在[Xamarin.Android Navigation Drawer(ㄧ)]文章中提到.Net的委派機制,這篇文章紀錄一下委派機制在
iOS裡面是如何實踐。
可以參考下方連結。
http://www.dotblogs.com.tw/toysboy21/archive/2015/02/02/148352.aspx
在設計iOS的APP上,委派代理機制是常常被使用到的。在一個ViewController裡面新增UITableView的時候就會碰到。可以參考下方iOS UITableView的文章。
http://www.dotblogs.com.tw/toysboy21/archive/2013/11/19/130485.aspx
這邊一樣用訂Pizza的故事來說明iOS的委派機制結構。
首先想像,假如你到Pizza Hat去訂一個Pizza。你告訴店員Pizza好的時候,通知我來拿。我們把這兩個元件用類別圖來表示的話,在iOS的design pattern應該會長的像底下這樣。
實際上要去拿Pizza,這個Move方法是掛在使用者(User類別)身上。你可以這樣想像,User類別把
Move這個方法委派給店家來觸發執行。也就是實際上,我們告訴店家當Pizza出爐的時候,請通知我來拿Pizza。
協定(Protocol)就是interface。其用來當你要委派別人處理事情,別人至少要有能力處這件事的能力,或者是知道要怎麼樣才能觸發你去處理某件事情。這個部分就仰賴協定(Protocal)來規範。
白話文:當我要委派PizzaHat來通知我去拿Pizza,PizzaHat至少要有知道如何通知我的能力。
UITableView的委派機制
已UITableView來說,在iOS開發時使用到UITableView,會在程式裡面宣告底下的語法,這是說把
UItableView的事件處理以及資料處理委派給自己(ViewController)。
self.UITableView.delegate = self;
self.UITableView.datasource = self;
那外部物件要怎麼得知與呼叫委派物件的方法呢?主要是依賴委派物件去實作某些特定的協定。
例如引用UITableView的ViewController.h檔案裡面必須要實作UITableViewDataSource與UITableViewDelegate兩個協定。當實作這兩個協定之後,在ViewController.m檔案中就要去實作相對應的方法。
//有多少Section
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
以及
//每個Section有多少Row
- (NSInteger)tableView:(UITableView *)tableViewnumberOfRowsInSection:(NSInteger)section
還有
//每個Row裡面的Cell資料
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
至於自己(self)已經被委派給UITableView.delegate還有UITableView.datasource。哪上述的三個方法什麼時候會被觸發呢?這就仰賴iOS底層幫我們處理掉了。所以我們不用去管怎麼被觸發的細節,只要專心在當這個事件被觸發的時候,我們要處理的常式。如同當按鈕事件被觸發時,我們只要專心地去管事件處理常式就好。
換個方式解釋,實際上你的手指頭在刷刷刷的滑動UITableView的時候,iOS幫我們處理了底層的一些判斷,並且去觸發了一些相對應的事件。而我們把整個UITableView Delegate給自己(self),所以我們只要在ViewController.m檔案去實作相對應的事件處理常式就可以。
建立Command Line專案
接著我們用一個簡單的範例來實踐iOS的委派。開啟Xcode新增一個Command Line Tool的專案。
宣告協定 Protocal (interface)
新增一個協定檔案,這個協定就是interface。在協定中定義了一個CallMeForTakePizza的方法。
PhoneCallProtocol.h
#import
//定義一個Protocal
@protocol getnotificationDelegate
@optional
- (void)callmefortakepizza:(NSString*)pizzatype;
@end
建立被委派的類別(PizzaHat)
建立一個類別檔案PizzaHat (Pizza店),在Pizza.h檔案宣告下方程式碼
首先再標頭檔案去引用協定檔案。
宣告一個delegate變數。這是一個存取子(Accessor)。回傳一個id<getnotificationDelegate>的物件指標,這邊指的就是外部物件(買Pizza的人)的記憶體位址。
Id本身可以把它看作是泛型物件的一種應用。而id<protocol>的宣告是指,這個物件必須要是有實作
<protocol>的物件。
例如id<getnotificationDelegate>這段描述的意思是,只你要傳入的物件必須是要實作getnotificationDelegate協定的物件。
另外宣告一個OnPizzaok方法。這個方法是用來呼叫外部物件的方法。用白話的方式來說,就是PizzaHat要去呼叫買Pizza的人走路來買Pizza時被觸發的方法。可以想像成Button 的TouchUpInside(click)事件。
PizzaHat.h 檔案
#import
#import "PhoneCallProtocol.h"
@interface PizzaHat : NSObject
@property (nonatomic,assign) id delegate;
-(void) OnPizzaok;
@end
在PizzaHat.m檔案的implementation的執行區塊,實作OnPizzaOK的方法。在這個方法中,呼叫
delegate指標物件的CallmeForTakePizza的這個方法。意思是,當客人的Pizza完成的時候,請通知他來拿Pizza。
PizzaHat.m 檔案
#import
#import "PizzaHat.h"
@implementation PizzaHat
@synthesize delegate;
-(void)OnPizzaok{
[delegate callmefortakepizza:@"夏威夷Pizza"];
}
@end
建立委派的類別(買Pizza的人)
宣告一個買Pizza的人的類別,在標頭的地方引用PhoneCallProtocol.h的Protocol。
這個類別要實作getnotificationDelegate這個協定(介面)。
User.h
#import
#import "PhoneCallProtocol.h"
@interface User : NSObject
@end
在User.m,因為在User.h檔案中實作了getnotificationDelegate這個協定。
所以就必須要去宣告getnotificationDelegate這個協定中所定義的callmefortakepizza這個方法的常式。
User.m
#import
#import "User.h"
@implementation User
-(void)callmefortakepizza:(NSString *)pizzatype{
NSLog(@"Ben先生,您的 %@ Pizza已經完成了",pizzatype);
}
@end
程式的撰寫
這個範例並不是在iOS專案下開發,是在終端機的模式下開發,所以在Main.m檔案中要宣告底下的程式。在int Main底下同時宣告並建立PizzaHat與User的物件實體。接著將user物件指派給
PizzaHat.delegate。然後呼叫[pizzaHat OnPizzaOK]方法。
main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
PizzaHat *pizzaHat = [[PizzaHat alloc]init];
User *user = [[User alloc]init];
pizzaHat.delegate = user;
[pizzaHat OnPizzaok];
}
return 0;
}
執行這個程式,可以看見當呼叫了[pizzaHat OnPizzaok]方法時,實際上觸發了
[user callmefortakepizza]這一個方法。
範例程式下載:https://github.com/twbenlu/iOSDelegatePizzaHat