Object-c Block的使用及說明

haibo wang發表於2014-04-10

Object-c 中的block就好像一段C函式般,由函式名,有返回值,有引數,由函式體等

1.簡單的block

  

1 ^(int A ,int B)
2     {
3         int C=A*B;
4         return C;
5     };

上述程式碼表示block有兩個整形引數A和B.在block體中進行A和B的相乘,將結果作為block的返回值返回出去。

2.將block作為引數的API

   在程式開發時,當需要一個NSArray物件的所有元素進行遍歷時,除了for迴圈,開發者可以使用block進行遍歷,程式碼如下:

1  NSArray *arrChar=[@"A/B/C/D/E/F" componentsSeparatedByString:@"/"];
2     //遍歷元素
3     [arrChar enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
4         NSLog(@"\nindex:[%d], value:[%@]",idx,obj);
5         if(idx == 4)
6         {
7             *stop=YES;
8         }
9     }];

這段程式碼執行後,在控制檯上留下了如下日誌:

index:[0], value:[A]
index:[1], value:[B];
index:[2], value:[C];
index:[3], value:[D];
index:[4], value:[E];

 這裡,NSArray物件所使用的API主要是一個對自己元素的遍歷功能,enumerateObjectsUsingBlock方法的引數需要是一個block物件,在NSArray的幫助文件中,我們找到了enumerateObjectsUsingBlock的宣告,內容如下:

-(void)enumerateObjectsUsingBlock:(void)(^(id obj,NSInterger idx,BOOL *stop))block

所傳入的3個引數中,前兩個是表明當前元素指標和序號的輸入引數,開發者直接拿來使用即可。最後一個引數講一個布林型別的指標傳遞進來,顯然是屬於一個典型的輸入引數。這個引數讓開發者可以靈活的控制遍歷的繼續執行是否。

3.block的宣告

  block在宣告時,需要遵循如下所示的格式結構。

  

<返回值型別> + (^<block名字>) + (<引數型別1>, <引數型別2>...)

 根據enumerateObjectsUsingBlock的結構,我們宣告的block變數物件如下:

void (^arrayEnumerateBlock)(id,NSUInteger,BOOL *);

 對於宣告完的block變數,可以直接進行初始化,也可以進行賦值。block的初始化格式如下:

void (^arrayEnumerateBlock)(id,NSUInteger,BOOL *)=^(id anObject, NSUInteger index, BOOL *isStop)
{
     NSLog(@"\nindex:[%d], value:[%@]", index, anObject);

     //序號4, 則停止遍歷
     if(index == 4)
     {
          *isStop=YES;
     }
};

上述程式碼中對於變數的初始化,開發者同樣需要加上“^”符號表明這是一個block型別。不過block的返回值和block名不用寫明,block名作為宣告的一部分已經在宣告時寫明,至於返回值,只需要在block體中嚴格遵守返回型別即可。

4.block 的 typedef

  在平時開發中,我們習慣於使用typedef定義屬於自己框架的東西,本質上其實是為了型別使用更加統一。

  而block由於有著相對複雜的宣告方式,不如可以考慮將block的格式進行typedef,之後所有需要遵循這個block格式的物件宣告再也不需要考慮格式的撰寫,並且由於block有著函式指標般的介面協議特徵,使用typedef定義過的block格式,當作為介面提供外部呼叫時,呼叫者可以明確具體需要給與的引數格式。

   同樣是enumerateObjectsUsingBlock所用的那個block格式,typedef定義示例如下

  

typedef void (^ArrayEnumerateBlockType)(id ,NSUInteger, BOOL *);

雖然typedef後接著的格式和前一小節中block宣告格式相當相似,不過typedef所書寫的內容是型別名字,而block宣告時所書寫的是變數物件的名字。所以在這裡,我們特意以ArrayEnumerateBlockType定義型別名和之前的arrayEnumerateBlock變數型別名加以區分。

一旦有了typedef過得block型別後,到哪裡都能夠簡單的使用這個block了,初始化程式碼如下:

ArrayEnumerateBlockType aEnumerateBlock=^(id aObject, NSUInteger index, BOOL *isStop)
{
     //跟前面一樣
}

 

5.block體的外部變數使用的奇怪之處

  首先,讓我們來看看以下程式碼。

 1     NSUInteger result   = 0;
 2     NSUInteger changeValue=0;
 3     //block變數宣告
 4     NSUInteger (^testReturnValueBlock)(NSUInteger, NSUInteger) = ^(NSUInteger param1, NSUInteger param2)
 5     {
 6         return param1+param2+changeValue;
 7     };
 8     
 9     result=testReturnValueBlock(1,2);
10     NSLog(@"1.[%d]",result);
11     
12     changeValue++;
13     result=testReturnValueBlock(1,2);
14     NSLog(@"2.[%d]",result);

執行結果卻是

1.[3]
2.[3]

 是不是覺得相當奇怪,我明明在第一次日誌列印後對changeValue變數執行了++操作,其實列印的結果並沒有錯,我們需要更深入的瞭解block內部機制才能夠看懂這其中的奧妙。

在block體中,我們不僅能夠訪問到block宣告的傳入引數,也能夠正常訪問到block以外的變數,不過,對於block以外的變數來說,如果把它放在block內進行訪問也罷賦值也罷,一旦進入了block體中,基本型別變數會被block進行一次copy後以一個臨時變數存放在block體中,而指標變數會被block進行一次retain後也以一個臨時變數存放起來,無論是基本資料型別還是指標,被block使用了,就表明他的生命週期除了自己本身所在的作用域,又多了一個block體的作用域,也就是說:

   (1)基本資料型別在block中的地址已經發生變化,所以block體外對於此資料型別的值修改對於體內的值毫無影響。

   (2)block所copy或者retain的變數,一旦block結束,也就一起跟著被釋放和銷燬了。

   (3)所謂的block會進行retain的指標型別,也包含Object-c中的所有物件。

6.克服外部變數的魔咒

   (1)可以把外部變數修改成  static NSUInteger changeVaue=0;

          static 關鍵字意味著changeValue的地址不再被我們放置於棧中,不過也並不在堆中,而是放在全域性資料區,擁有一個永遠不會改變的地址。

   (2)也可以在外部變數前加上 __block   如:  __block NSUInteger changeValue=0;

          當一個變數被__block所修飾時,block體中就會知道,即使使用到這個外部變數,也堅決不會去進行retain或者copy,這樣就能夠保證內外統一了

相關文章