1. 程式人生 > >淺談iOS中的閉包

淺談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]);
}