2015年11月25日 星期三

[iOS] kkbox-ios-dev note Data Source與Delegate的差別?

我們現在可以來看看UITablewView與UItableViewController是怎麼運作的。
UITableViewController在loadView中建立了一個UITableView的instance,
指定成是自已的view,同時將這個view的delegate與data source設定成自已。
一個class可以根據需要,將delegate拆成好幾個,以UITableView來說,
跟表格中有什麼資料有關的,就放在data source中,其餘的method就放在delegate中。

我們在Mac OX X會用到的最龐大的UI元件,莫過於WebView,雖然在iOS上UIWebView被閹割到只剩下四個delegate method,但是Mac OS X上足足有五大類delegate method,網頁頁框的載入進度、個別圖片檔案的載入進度、下載檔案的UI呈現,該不該開視窗或新分頁、沒有安裝Java或是Flash要怎麼呈現、用JavaScript跳出alert該怎麼呈現...都是一堆delegate  method。

假如先不管UITableView怎麼重複使用UITableViewCell的機制(這個機制還挺複雜),
我們要更新UITableView的資料時,先指定data source物件後,要呼叫一次reloadData。
reloadData可能是這樣寫的:
- (void)reloadData {
    NSInteger sections = 1;
    if([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
        sections = [dataSource numberOfSectionsInTableView:self];
    }

    for (NSInteger section = 0; section < sections; section++) {
        NSInteger rows = [dataSource tableView:self numberOfRowsInSection:section];
        for (NSInteger row = 0; row < rows; row++) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
            UITableViewCell *cell = [dataSource tableView:Self cellForRowAtIndexPath:indexPath];
        }
    }
}
我們注意到幾件事情:首先,因為numberOfSectionsInTableView:被定義成optional的
delegate method, delegate不見得要實作,所以我們會用respondsToSelector:
檢查是否有實作。我們可以在protocol的宣告中,指定某個delegate method是required或是optional,如果不特別指定的話,預設都是required。我們簡單看一下
UITableViewDataSource就知道如何定義required與optional的delegate method。

@protocol UITableViewDataSource <NSObject>
@required
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableVIew cellForRowAtIndexPath:(NSIndexPath *)indexPath;

@optional
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

@end

另外,就是定義在data source的method,是在reloadData中被呼叫,因此我們可以知道 UITableView的data source與delegate的最大差別:我們絕對不可以在data source定義的method中呼叫reloadData,不然就會進入無窮廻圈!

Formal Protocol與Informal Protocol
@protocol這個關鍵字是在Objective-C2.0之後出現的,在這之前要定義protocol,則是寫成NSObject的category,前者叫做formal protocol,後者則稱為informal protocol。
UIKit問世時就採用Objective-C2.0的語法,至於Max OS X,蘋果在2008年開始大幅改寫
Foundation與AppKit,現在絕大多數可以看到的protocol都是formal protocol,但如果
你在maintain一份稍微有點歷史的程式,或是在蘋果少數的API中,還是可以看到informal protocol。在Core Animation裡頭,就可以看到CALayerDelegate、CALayoutManager、CAAnimationDelegate,都還是informal protocol。其中CALayerDelegate、CALayoutManager兩者之間還夾著CAAction這個formal protocol。在兩個informal protocol中間夾著一個formal protocol,實在讓人很反感,為什麼不一起改掉呢?至於CAAnimationDelegate也很怪異:CAAnimation的delegate不是用assign,而是會retain起來。

無所不在的Delegate
由於在Objective-C語言中,delegate相當於event handler的用途,所以,當你在其他平台中看到event handler用得多頻繁,就等於delegate用得多頻繁。
舉例來說:
* 在使用NSURLConnection抓取網路上的資料的時候,無論收到了HTTP response code、是否連線失敗,是否連線結束...都是透過delegate回傳。
* 在使用Core Location的時候,如果CLLocationManager找到了我們的所在位置,或是發現我們正在移動,也都會透過delegate通知。
* 當我們要使用手機拍照、傳送簡訊或是電子郵件等等,當照片拍完,會用delegate回傳image物件,簡訊或是電子郵件傳送成功,也會用delegate告訴我們執行完畢。

甚至當我們在寫一個iOS程式的第一步,其實都是在實作一個delegate mehtod。
我們在Xcode裡頭開了一個新專案之後,下一步往往是實作
application:didFinishLaunchingWithOptions:這個method,但是要了解整個程式的進入點,我們要從main.m來看。裡頭通常只有簡短幾行:
int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
一個iOS程式是從main這個function開始,接著透過呼叫UIApplicationMain建立UIApplication這個Singleton物件UIApplication用來代表一個應用程式的基本狀態,包括icon上面該顯示多少push notification的數量、支援水平還是郵直畫面、是否顯示狀態列等,當UIApplication物件被建立起來後,就要通知它的delegate,程式已經開啟了,進行下一步,這個delegate method就是application:didFinishLaunchingWithOptions:,我們在這邊建立基本的view controller與window,顯示出來。
也就是說,當我們在開始寫第一行iOS程式的時候,我們就起碼需要了解什麼是Singleton和delegate,但是在了解之後,想要知道Mac OS X與iOS中眾多的元件如何使用,以及怎樣用比較好的方式設計自已的元件,就不是問題了。

其他平台上所謂的Delegate
在其他平台中,也用到了delegate這個詞,但是意義不太一樣。
Design Pattern中所講的Delegate
就我的理解,Design Pattern中所講的Delegate Pattern,比較像是做一個Wrapper,
有一個class在實作method時,其實是直接把這個method的實作傳遞到自已的成員變數物件的實作上。以Objective-C語言實作會像這樣:
首先產生一個內部的物件,叫做MyInnerClass:

@interface MyInnerClass : NSObject
- (void)doSomething;

@end

@implementation MyInnerClass

- (void)doSomething {
    NSLog(@"Do something");
}

@end
然後MyClass會把該做的事情,都交給MyInnerClass:

@interface MyClass : NSObject {
    MyInnerClass *innerObject;
}

- (void)doSomething;

@end

@implementation MyClass

- (void)dealloc {
    [innerObject release];
    [super dealloc];
}

- (id)init {
    self = [super init];
    if (self) {
        innerObject = [[MyInnerClass alloc] init];
    }

    return self;
}

- (void)doSomething {
    [innerObject doSomething];
}

@end
在Cocoa Framework中,會比較像是NSButton與NSButtonCell的關係。你或許會問,為什麼Objective-C裡頭的delegate與Design Pattern裡頭講的Delegate Pattern意義不一樣呢?為什麼Objective-C不按照這套用法?但其實是,Objective-C使用delegate這個觀念,早於Design Pattern成書。

C#中所謂的Delegate
C#語言中也有delegate這個關鍵字、不過用途卻是處理anonymous function,以我們上面的例子,我們打C#增加被點選的event handler,原本這麼寫:

private void InitializeComponent() {
    this.button1 = new System.Windows.Forms.Button();
    this.button1.Click += Button1_Click;
}

private void Button1_Click(object sender, System.EventArgs e) {

}

在C#2.0可以寫成這樣:
private void InitializeComponent() {
    this.button1 = new System.Windows.Forms.Button();
    this.button1.Click += delegate(object sender, System.EventArgs e) {
        // Do something here.
    }
}

關於在Objective-C語言中怎麼使用anonymous function,我們會在接下來的章節,講block的時候討論。

沒有留言:

張貼留言