許多人在開發 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 的語法其實不是很容易理解,但相信你只要多練習,一定可以看得他在幹麼的。
[…] Objective-C 中的 Block 語法教學 […]