Objective-C 高級編程-block

在前兩篇中,我們介紹了一些關(guān)于C語言的重要概念,指針struct,這些基礎(chǔ)知識是我們深入學習block的前提,在這里將盡可能的將關(guān)于block的相關(guān)知識講解的詳細一些,分享給那些想要提升自己的小伙伴們,文章會比較長,一下子寫完實在是有些力不從心,所以會采取不斷更新的方式。

首先看一下什么是Blocks

Blocks是C語言的擴充功能。可以用一句話來表示Blocks的擴充功能:帶有自動變量(局部變量)的匿名函數(shù)。

顧名思義,所謂匿名函數(shù)就是不帶有名稱的函數(shù)。C語言的標準不允許存在這樣的函數(shù)。例如如下的源代碼:

int func(int count);

它聲明了名為func的函數(shù)。下面的源代碼中為了調(diào)用該函數(shù),必須使用該函數(shù)的名稱func。

int result = func(10);

如果像下面這樣,使用函數(shù)指針來代替直接調(diào)用函數(shù),那么似乎不用知道函數(shù)名也能夠使用該函數(shù)。

int result = (*funcptr)(10);

但其實使用函數(shù)指針也仍然要知道函數(shù)名稱。像以下的源代碼一樣,在賦值給函數(shù)指針時,若不使用想賦值的函數(shù)的名稱,就無法獲得該函數(shù)的地址。

int (*funcptr)(int) = &func;
int result = (*funcptr)(10);

而通過Blocks,源代碼中就能夠使用匿名函數(shù)了,即不帶名稱的函數(shù)。對于程序員而言,命名就是工作的本質(zhì),函數(shù)名、變量名、方法名、屬性名、類名和框架等都必須具備。而能夠編寫不帶名稱的函數(shù)對程序員來說相當具有吸引力。

到這里,我們知道了“帶有自動變量值的匿名函數(shù)”中“匿名函數(shù)”的概念。那么“帶有自動變量值”究竟是什么呢?

首先回顧一下在C語言的函數(shù)中可能使用的變量。(以下的變量在內(nèi)存中的分配是不一樣的,可以參考這篇文章了解 C語言中的內(nèi)存分配。)

  • 自動變量(局部變量)
  • 函數(shù)的參數(shù)
  • 靜態(tài)變量(靜態(tài)局部變量,方法中用static聲明的變量)
  • 靜態(tài)全局變量(方法外用static聲明的變量)
  • 全局變量

其中,在函數(shù)的多次調(diào)用之間能夠傳遞的變量有:

  • 靜態(tài)變量(靜態(tài)局部變量)
  • 靜態(tài)全局變量(只能在本文件中使用的全局變量)
  • 全局變量(外接也可以使用的全局變量)
    雖然這些變量的作用域不同,但在整個程序中,一個變量總保持在一個內(nèi)存區(qū)域。因此,雖然多次調(diào)用函數(shù),但該變量值總能保持不變,在任何時候以任何狀態(tài)調(diào)用,使用的都是同樣的變量值。

另外,“帶有自動變量值的匿名函數(shù)”這一概念并不僅指Blocks,它還存在于其他許多程序語言中。

程序語言 Block的名稱
C + Blocks Block
Smalltalk Block
Ruby Block
LISP Lambda
Python Lambda
C++11 Lambda
Javascript Anonymous function

Blocks 模式

一、Block語法

下面我們詳細講解一下帶有自動變量值得匿名函數(shù)Block的函數(shù),即Block表達式語法。先看一個簡單點的形式:

^(int event){
        printf("event:%d",event);
}

實際上,上述Block語法使用了省略方式,其完整形式如下:

^void(int event){
        printf("event:%d",event);
}

如上所示,完整形式的Block語法與一般的C語言函數(shù)定義相比,僅僅有兩點不同。

  • 沒有函數(shù)名
  • 帶有“^”
    第一點不同是沒有函數(shù)名,因為它是匿名函數(shù)。第二點不同是返回值類型前帶有“^”(插入記號,caret)記號。因為OS X、iOS應(yīng)用程序的源代碼中將大量使用Block,所以插入該記號便于查找。

Block的一般范式如下

^ 返回值類型   參數(shù)列表   表達式

返回值類型同C語言函數(shù)的返回值類型,“參數(shù)列表”同C語言的參數(shù)列表,“表達式”同C語言函數(shù)中允許使用的表達式。當然與C語言函數(shù)一樣,表達式中含有return語句時,其類型必須與返回值類型相同。

例如寫出如下形式的Block語法:

^ int (int count){return count+1};
返回值的類型可以省略

雖然前面出現(xiàn)過省略方式,但Block的語法可省略好幾個項目。首先是返回值類型,如下所示:

^ 參數(shù)列表 表達式(返回值類型被省略)

當你省略返回值類型時,如果表達式中有return語句就使用該返回值的類型,如果表達式中沒有return語句就使用void類型。表達式中含有多個return語句時,所有return的返回值類型必須相同。

前面的^ int (int count){return count+1};源代碼省略其返回值類型時如下所示:

^ (int count){return count+1};

該block語法將按照return語句的類型,返回int形的返回值。

如果不使用參數(shù),參數(shù)列表也可以省略。以下為不使用參數(shù)的Block語法:
^ void(void){printf("blocks\n")};

可以進一步的省略:

^ {printf("blocks\n")};
二、Block類型變量

上面提到的Block語法單單從其技術(shù)方式上來看,除了沒有名稱以及帶有“^”以外,其他都與C語言函數(shù)定義相同。在定義C語言函數(shù)時,就可以將所定義函數(shù)的地址賦值給函數(shù)指針類型變量中。

int func(int count){
        return count+1;
}
int (*funcptr)(int) = &func;

這樣一來,函數(shù)func的地址就能賦值給函數(shù)指針類型變量funcptr中了。
同樣地,在Block語法下,可將Block語法賦值給聲明為Block類型的變量中。即源代碼中一旦使用Block語法就相當于生成了可賦值給Block類型變量的“值”。Blocks中由Block語法生成的值也被稱為“Block”。在有關(guān)Blocks的文檔中,“Block”既指源代碼中的Block語法,也指由Block語法所生成的值。

聲明Block類型變量的示例如下:

int (^blk)(int);

與前面的使用函數(shù)指針的源代碼對比可知,聲明Block類型變量僅僅是將聲明函數(shù)指針類型變量*變?yōu)?code>^。該Block類型變量與一般的C語言變量完全相同,可作為以下用途使用。

  • 自動變量
  • 函數(shù)參數(shù)
  • 靜態(tài)變量
  • 靜態(tài)全局變量
  • 全局變量

那么,下面我們就試著使用Block語法將Block賦值為Block類型變量。

int (^blk)(int) = ^(int count){
      return count +1;
};

由“^”開始的block語法生成的Block被賦值給變量blk中。因為與通常的變量相同,所以當然也可以由block類型變量向block類型變量賦值。

int (^blk1)(int) = blk;//聲明一個block類型變量blk1,并將blk賦值給blk1。
int (^blk2)(int);//聲明一個block類型變量blk2。
blk2 = blk1;

在函數(shù)參數(shù)中使用Block類型變量可以向函數(shù)傳遞Block。

void func(int(^blk)(int)){}//int(^blk)(int)可以看做是一個block類型的變量blk。

在函數(shù)返回值中指定block類型,可以將block作為函數(shù)的返回值返回。

int (^func())(int){
return ^(int count){
      return count+1
};//block類型為int (^func()),參數(shù)列表為空,不是沒有參數(shù),而是可以任意參數(shù)。返回的^(int count){return count+1}可以作為是上述類型的block變量。
}

從上面可以看到,在函數(shù)參數(shù)和返回值中使用Block類型變量時,記述方式比較復雜,這時候,可以向使用函數(shù)指針類型時那樣,使用typedef來解決該問題。

typedef int(^blk_t)(int);

如上所示,通過使用typedef可以聲明“blk_t”類型變量。我們試著在上面例子中的函數(shù)參數(shù)和函數(shù)返回值部分里使用一下。


/*原來的記述方式
void func(int (^blk)(int))
*/
//現(xiàn)在的
void func(blk_t blk)

/*原來的記述方式
int (^func()(int))
*/
//現(xiàn)在的
blk_t func();

通過使用typedef,函數(shù)定義就變得更容易理解了。
另外,將賦值給Block類型變量中Block方法像C語言通常的函數(shù)調(diào)用那樣使用,這種方法與使用函數(shù)指針類型變量調(diào)用函數(shù)的方法幾乎完全相同。變量funcptr為函數(shù)指針類型時,像下面這樣調(diào)用函數(shù)指針類型變量。

int result = (*funcptr)(10);

變量blk為Block類型的情況下,這樣調(diào)用block類型變量:

int result = blk(10);

通過Block類型變量調(diào)用Block與C語言通常的函數(shù)調(diào)用沒有區(qū)別。在函數(shù)參數(shù)中使用Block類型變量并在函數(shù)中執(zhí)行Block的例子如下:

int func(blk_t blk,int rate){
return blk(rate);
}

當然,在Objective-C的方法中也可使用

- (int)methodUsingBlock:(blk_t)blk rate:(int)rate{
      return blk(rate);
}

Block類型變量可完全像通常的C語言變量一樣使用,因此也可以使用指向Block類型變量的指針,即Block的指針類型變量。

typedef int (^blk_t)(int);
blk_t blk = ^(int count){
      return count +1;
}
blk_t *blkptr = &blk;
(*blkptr)(10);

由此可知Block類型變量可像C語言中其他類型變量一樣使用。

三、截獲自動變量值

通過Block的語法和Block類型變量的說明,我們已經(jīng)理解了“帶有自動變量值的匿名函數(shù)”中的“匿名函數(shù)”。而“帶有自動變量值”究竟是什么呢?“帶有自動變量值”在Blocks中表現(xiàn)為“截獲自動變量值”。截獲自動變量值的實例如下:

int main(){
  int dmy = 256;
  int val = 10;
  const char *fmt = "val = %d\n";
  void (^blk)(void) = ^{printf(fmt,val)};
  val = 2;
  fmt = "These values were changed . val = %d\n";
  blk();
  return 0;
}

該源代碼中,Block語法的表達式使用的是它之前聲明的自動變量fmt和val。Blocks中,Block表達式截獲所使用的自動變量的值,即保存該自動變量的瞬間值。因為Block表達式保存了自動變量的值,所以在執(zhí)行Block語法后,即使改寫B(tài)lock中使用的自動變量的值也不會影響B(tài)lock執(zhí)行時自動變量的值。該源代碼就在Block語法后改寫了Block中的自動變量val和fmt。下面我們一起看下執(zhí)行結(jié)果。

val = 10;

執(zhí)行結(jié)果并不是改寫后的值,而是執(zhí)行Block愈發(fā)時候的自動變量的瞬間值。該Block語法在執(zhí)行時,字符串指針被賦值到自動變量fmt中,int值10被賦值到自動變量val中,因此這些值被保存(即被截獲),從而在執(zhí)行塊時使用。
這就是自動變量值的截獲。


四、__block 說明符

實際上,自動變量值截獲只能保存執(zhí)行Block語法瞬間的值。保存后就不能改寫該值。下面我們嘗試改寫截獲的自動變量值,看看會出現(xiàn)什么結(jié)果。下面的源代碼中,Block語法之前聲明的自動變量val值被賦予1。

int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf ("val = %d\n",val);

以上為在Block語法外聲明的給自動變量賦值的源代碼。該源代碼會產(chǎn)生編譯錯誤。

error: variable is not assignable(missing __block type specifier)
void (^blk)(void) =  {val  =  1; }; 
                      ~~~  ^

若想在Block語法的表達式中將值賦給在Block語法外聲明的自動變量,需要在該自動變量上附加__block說明符。該源代碼中,如果給自動變量聲明int val附加__block說明符,就能實現(xiàn)在Block內(nèi)賦值。

__block int val = 0;
void(^blk)(void) = ^{val = 1;};
blk();
printf("val = %d\n",val);

該源代碼的執(zhí)行結(jié)果為

val = 1;

使用附有__block說明符的自動變量可在Block中賦值,該變量稱為__block變量。

五、 截獲的自動變量

如果將值賦值給Block中截獲的自動變量,就會產(chǎn)生編譯錯誤。

int val = 0;
void(^blk)(void) = ^{val = 1;}

該源代碼會產(chǎn)生以下編譯錯誤:

error: variable is not assignable(missing __block type specifier)
void (^blk)(void) =  {val  =  1; }; 
                      ~~~  ^

那么截獲Objective-C對象,調(diào)用變更該對象的方法也會產(chǎn)生編譯錯誤么?

  id array = [[NSMutableArray alloc]init];
  void (^blk)(void) = ^{
    id obj = [[NSObject alloc]init];
    [array addObject:obj];
  };

這是沒有問題的,而向截獲的變量array賦值會產(chǎn)生編譯錯誤。該源代碼中截獲的變量值為NSMutableArray類的對象。如果用C語言來描述,即是截獲NSMutableArray類對象用的結(jié)構(gòu)體實例指針。雖然賦值給介乎跌自動變量array的操作會產(chǎn)生編譯錯誤,但使用截獲的值卻不會有任何問題。下面的源代碼向截獲的自動變量進行賦值,因此會產(chǎn)生編譯錯誤。

  id array = [[NSMutableArray alloc]init];
  void (^blk)(void) = ^{
        array = [[NSMutableArray alloc]init];
  };

error: variable is not assignable(missing __block type specifier)
        array = [[NSMutableArray alloc]init];
         ~~~  ^

這種情況下,需要給截獲的自動變量附加__block說明符。

__block id array =  [[NSMutableArray alloc]init];
  void (^blk)(void) = ^{
        array = [[NSMutableArray alloc]init];
  };

接下來會介紹block是如何實現(xiàn)的,會從它的c++源碼層面進行分析。(未完待續(xù)。。)

參考書籍:Objective-C高級編程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容