2014年3月18日 星期二

Objective-C Block 運用

這篇文章試圖用一個很簡單的範例來介紹:在 Objective-C 中怎麼利用 Block 進行 API 的設計。這篇文章的小小價值,在於不從 Block 的語法、特性切入,而是直接以 API 設計的思考做為起點來舖陳,希望閱讀者能盡快抓到 Block 用於設計的精要。

一、起點:以「書名」來找一本書

目前 API 還沒有任何一行 code 被產生。沒關係,我們先在 API 的 client  寫下欲設計的結果。

    for (NSString *bookName in @[@"Objective-C 真簡單", @"Objective-C 學不會"]) {
        [self printReportHeader:bookName];
        [BookFinder bookWithName: bookName          //以書名找書
                   actionIfFound: foundAction       //若有找到書,就做 foundAction
                actionIfNotFound: notFoundAction];  //若找不到書,就做 notFoundAction
    }

    - (void)printReportHeader:(NSString *)bookName
    {
       NSLog(@"======================================");
        NSLog(@"==> 欲尋找的書籍: %@", bookName);
    }

上面就是我想要的結果:一個 BookFinder 以 bookWithName:actionIfFound:actionIfNotFound 這個 Class Method 查找書籍。這個 Method 以書名(bookName)作為參數傳入,若書有找到,就執行 actionIfFound;反之,沒找到書,就執行 actionIfNotFound。

注意,上面我用「執行」作為及物動詞,而 actionIfFound 和 actionNotFound 為受詞。可見這兩個 action 不是單純的 object instance;事實上,它們是 block 。

繼續,我們進一步設計 actionIfFound 和 actionNotFound 這兩個 block 的工作內容。這兩個 block 是所謂的 in-line block :

    void (^foundAction)(Book *) = ^(Book *book) {
        NSLog(@"==> 書籍售價: %@", book.price);
    };
    
    void(^notFoundAction)(BookInfo *) = ^(BookInfo *bookInfo) {
        NSLog(@"==> 找不到的原因: %@", bookInfo.reason);
    };

這裡設計:若找到書,則把 Book 的 object 作為參數傳入 block ,我們就可以印出書籍的價格;若沒找到書,block 內應該能從 BookInfo 的 object 中取得找不到的原因並印出來。

然後我預想一下我要的輸出結果:

 ======================================
 ==> 欲尋找的書籍: Objective-C 真簡單
 ==> 書籍售價: 100
 ======================================
 ==> 欲尋找的書籍: Objective-C 學不會
 ==> 找不到的原因: 絕版

嗯,差不多了,開始把其他的程式碼長出來吧!

in BookFinder.h

宣告出我們在 Client 中使用的 Class Method :

+(void) bookWithName:(NSString *)name actionIfFound:(BookFindAction)actionIfFound actionIfNotFound:(BookNotFindAction)actionIfNotFound;

上面的 BookFindAction 和 BookNotFindAction 是自訂的 Block,使用 C 語言的 typedef 進行定義:

typedef void(^BookFindAction)(Book *);
typedef void(^BookNotFindAction)(BookInfo *);

從上式可知,這兩個 Block 都沒有回傳值 (void),傳入的參數則是遵照在 Client 端程式碼怎麼用它們來設計。記得,上面兩式得寫在 @interface BookFinder : NSObject 區段的上方才行哦!


in BookFinder.m

+(void) bookWithName:(NSString *)name actionIfFound:(BookFindAction)actionIfFound actionIfNotFound:(BookNotFindAction)actionIfNotFound
{
    Book *book = [BookFinder findByName: name];
    
    if (book != nil) {
        actionIfFound(book);
    } else {
        BookInfo *bookInfo = [BookInfo new];
        bookInfo.reason = @"絕版";
        actionIfNotFound(bookInfo);
    }
}

+(Book *)findByName:(NSString *)name
{
    if ([name isEqual:@"Objective-C 真簡單"]) {
        Book *book = [Book new];
        book.name = name;
        book.price = @100;
        return book;
    }
    
    return nil;

}

這裡的實作很簡單,透過一個簡單的比對,只要書名不是「Objective-C 真簡單」,就當那本書絕版。其餘的實作邏輯也是完全遵照一開始在 Client 端程式碼的設定。

至於 Book 和 BookInfo 只是兩個設定了 Property 的簡單類別,就不列出來佔版面了。

上面所謂「Client 端的程式碼」,若是寫在 Unit Test 中當然更好,不但能更佳的突顯、收納設計意圖,也能時時驗證。

這裡展示的是如何在對於 Block 最少量所知、而且尚未有任何程式碼前進行設計,並一步步把設計實現的思維及步驟,希望對於尚未使用 Block 在自己的程式裡進行設計的人能有所助益。


沒有留言:

張貼留言