Objective-C 中的 Block 語法教學

許多人在開發 Objective-C 程式,最不懂的就是 Objective-C 中的 block 語法,而 Objective-C 中的 block 語法其實跟 Swift 的 Closures 或是 C# 的 lambda 很像,都是一種函式簡化的過程及 Callback,而國外甚至有了人成立 fuckingblocksyntax 網站,來幫助程式開發者快速看懂 Block 的宣告與使用。但我相信許多人跟我一樣,看了他的宣告後,還是看不懂該怎麼用,而這篇文章,將教你如何在 Objective-C 使用 Block  喔!

 

就海芋的用習慣而言,海芋會先把一個要使用的 Block  使用 typedef 宣告起來,讓其它的函式更好去呼叫,所以我們先看一下如何使用 typedef 宣告 Block。

 typedef returnType (^TypeName)(parameterTypes);

 

舉例來說,假設海芋的程式有一個「左右的按鈕元件」,當海芋要進行按鈕點擊,就要發送一個事件,通知畫面是按了左邊按鈕還是右邊按鈕,這時我這個 Block 就有一個別名,叫做 BottonClick 了,而且返回的型態是 void。

typedef void (^BottonClick)(BOOL isRight);

 

在畫面端,並且使用剛才的別名「BottonClick」 來產生一個函式,以下個例子來說,就是告訴畫面,如果按了「右側按鈕」或是「資料沒有修改」,則直接不做任何事。要注意的是為了避免 Memory leaks,在 Block 中呼叫自己程式內的其它函式,都必須使用弱型別的方式呼叫,可以宣告成「__typeof__(self) __weak me = self;」,再使用「me xxx」 來呼叫自己的函式

- (BottonClick) genBottomViewCallback
{
    __typeof__(self) __weak me = self;
 
    return ^(BOOL isRight) {
        if (isRight){
            return;
        }
 
        if (NO == [me isDatModified]){
            return;
        }
 
        [me onNextButtonClick];
    };
}

 

接下來,依海芋的習慣,會將傳進去的 Block 以變數來儲存,而如何將 Block 以變數的方式儲存呢?我們先看一下儲存方法。

@property (nonatomic, copy) returnType (^blockName)(parameterTypes);

 

所以,以這個例子來說,就會是以下的儲存方式,注意,這裡一定要宣告成「copy」,不可以使用「strong」或是「 assign」等。這也是為什麼我們一定要在 block 中用「弱型別」去呼叫自己程式的其它函式。

@property(nonatomic, copy) BottonClick m_ButtonClickCallback;

 

當然也可以用 function 的方式傳 Block 參數,以下是他的傳遞方式。

- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;

 

如果你有使用 typedef 的話,一切都變得更簡單。

- (void)someMethodThatTakesABlock:(typdef alies)blockName;

 

以本例來說,只要在函式中使用「ButtonClick」的參數型別,並且配合參數,就可以將外部的 Block 存成本地的變數,也只要用「self.BlockName = block」來設定就好了

- (void) setBlock: (ButtonClick) block
{
    self.m_ButtonClickCallback      = block;
}

 

還記得前面我們曾經宣告過「ButtonClick」這個 Block 嗎?這個 Block 有一個變數,是來告訴前端使用者是按下左側按鈕還是右側按鈕的變數。所以,如果我們按下了左側按鈕,只要呼叫這一個 Block 就可以了。

if (self.m_ButtonClickCallback == nil){
     return;
}    
self.m_ButtonClickCallback(NO);

 

所以,在前端我們可以寫成這樣,以本文的範例來說,當使用者按下「左側按鈕」時,就會透過 genBottomViewCallback 這個函式,來繼續呼叫程式內的其它函式囉。

BottomViewModel* viewModel = [[BottomViewModel alloc] init];
[viewModel setBlock: [self genBottomViewCallback]];

 

而如果要說到最典型的 Block 語法,很常用的就是 Objective-C 的陣例排序 所用到的比較 Block,以下是排序的範例。

NSComparator compare = ^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        NSString* strObj1 = (NSString*) obj1;
        NSString* strObj2 = (NSString*) obj2;
        NSNumber* number1 = @([strObj1 doubleValue]);
        NSNumber* number2 = @([strObj2 doubleValue]);
 
        return [number1 compare: number2];
    };

 

但如果你仔細去推 NSComparator 的型態,會發現其實他的型態如下。

typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);

 

再看一次如何儲存 Block 起來,以上面的範例來說,blockName 就是 NSComparator,returnType 就是 NSComparisonResult。

returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

 

也就是說,NSComparator 這個 Block 的回傳型態其實是 NSComparisonResult,而這個 Block 要回傳什麼,就靠 obj1 和 obj2 去相比所得到的結果。也因此我們用來比較 block 才必須以「^NSComparisonResult」開頭,並寫成這樣去儲存。

^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        NSString* strObj1 = (NSString*) obj1;
        NSString* strObj2 = (NSString*) obj2;
        NSNumber* number1 = @([strObj1 doubleValue]);
        NSNumber* number2 = @([strObj2 doubleValue]);
 
        return [number1 compare: number2];
    };

 

總結:Block 的語法其實不是很容易理解,但相信你只要多練習,一定可以看得他在幹麼的。