淺談iOS中的閉包
1.1 用途
閉包在很多語言中都有應用,它在OC中被叫做Blocks
,在Java中被叫做Lambda表示式
,也有直接叫做匿名函式的。
簡單的說閉包就是一種帶有區域性變數的匿名函式。
在C語言中,函式可以通過函式名直接呼叫,也可以通過函式指標呼叫,但是這都需要開發者知道函式的名字(函式指標也需要知道函式名以便在被賦值時得到函式的地址)。
可能你會問,為什麼要用閉包呢?一個常見的例子如下:
實現按鈕的回撥方法。
int buttonId = 0; void buttonCallBack(int event){ NSLog(@"id = %d,event = %d",buttonId, event); }
現在把情況擴充套件到多個按鈕,如下:
void buttonCallBack(int buttonId, int event){
NSLog(@"id = %d,event = %d",buttonId, event);
}
void setButtonCallbacks(){ //工廠方法
for (int i = 0; i < MAX; i++) {
buttonId = i;
setButtonCallBack(i,&buttonCallBack); //省略單個set的方法了,只為說明思路
}
}
顯然回撥方法儲存了按鈕的ID以及回撥函式的指標。閉包的出現可以使程式碼更加簡潔,可以直接將回調解除安裝函式內,而不用再去寫回調函式,例子如下:
void setButtonCallbacks(){
for (int i = 0; i < MAX; i++) {
setButtonCallbackUsingBlock(i,^(int event){
NSLog(@"id = %d,event = %d",buttonId, event);
});
}
}
注意:當用於函式引數時,Block 應該放在引數列表的最後一個。
下面介紹Blocks
的語法:
1.2 語法
Blocks
的語法有些晦澀,以至於有fuckingblocksyntax這個網站專門記錄語法。
如下是Blocks
^
返回值型別
引數列表
表示式
比如:
1
|
^int (int count){return count + 1;}
|
Blocks
是可以進行縮寫的,如下
1.2.1 省略返回值型別
當省略返回值型別時,如果反表示式中又return
語句就使用該返回值的型別,如果表示式沒有return
語句就是void
型別。
如果有多個return
語句,那麼其型別必須相同。省略返回值型別後,例子如下:
1
|
^(int count){return count +1};
|
1.2.2 省略引數引數列表
如果不使用引數,引數列表也可以省略,例子如下:
1
|
^void (void) {NSLog(@"helloworld");}
|
可以省略 返回值型別
與引數列表
縮寫為如下的形式:
1
|
^{NSLog(@"helloworld");}
|
1.2.2 Block 型別
與C語言中的變數相同,Block
型別的變數可以作一下用途。
- 區域性變數
- 函式引數
- 靜態變數
- 靜態全域性變數
- 全域性變數
如下是一個常見的宣告Block
型別的變數的例子:
1
|
int (^blk) (int) = ^(int count){return count + 1;}
|
當然,在函式引數中使用Block
型別的變數就可以向函式傳遞Block
,在函式返回值中指定Block
型別,可以將Block
作為函式的返回值返回。分別對應如下的兩個例子:
1
|
void function (int (^blk) (int))
|
1 2 3 |
int (^func())(int) { return ^(int count){return count + 1;} } |
到這裡,Block
的語法變得著實複雜了,可以通過typedef
做簡化。如下是簡化的例子:
1 2 3 4 5 6 7 8 9 10 11 |
typedef int (^blk_t) (int); //原來的寫法 void func(int (^blk) (int)) //新的寫法 void func(blk_t blk) //原來的寫法 int (^func()(int)) //新的寫法 blk_t func() |
1.2.3 捕獲外部變數
Block
中捕獲外部的區域性變數具有瞬間性,即如果變數被Block
捕獲後修改了值,那麼Block
中捕獲的變數的值並不會改變。
此外,Block
無法給捕獲的外部變數賦值。
1.2.4 __block修飾符
Block
捕獲外部的區域性變數後,無法改變它的值,使用附有 __block
修飾符的區域性變數可以在Block
中賦值。
1.2.5 注意事項
Block
中雖然無法給捕獲的區域性變數賦值,但是對於OC的物件的一些方法,是可以執行的,比如捕獲一個NSMutableArray後,執行addObject
方法。這不會有任何問題,因為這相當於捕獲了物件的例項指標。
對於C語言中的陣列,Block
中並沒有實現對之的捕獲方法。可以使用指標來解決這個問題。
1 2 3 4 5 |
char *text = "helloworld"; void (^blk)(void) = ^{ NSLog(@"%c",text[2]); } |