Object-c Block的使用及說明


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,這樣就能夠保證內外統一了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM