本文轉(zhuǎn)載于 http://segmentfault.com/a/1190000003093017, 如有侵權(quán),請原著與我溝通,可以立即取消發(fā)布。
什么是Block
Blocks 是 iOS 4.0 之后有的新功能。
Block 能夠讓我們的代碼變得更簡單,能夠減少代碼量,降低對于 delegate 的依賴,還能夠提高代碼的可讀性。
本質(zhì)上來說,一個 Block 就是一段能夠在將來被執(zhí)行的代碼。本身 Block 就是一個普通的 Objective-C 對象。正因為它是對象,Block 可以被作為參數(shù)傳遞,可以作為返回值從一個方法返回,可以用來給變量賦值。
在其他語言(Python, Ruby, Lisp etc.)中,Block 被叫做閉包——因為他們在被聲明的時候的封裝狀態(tài)。Block 為指向它內(nèi)部的局部變量創(chuàng)造了一個常量 copy。
在 Block 之前,如果我們想要調(diào)用一段代碼,然后之后一段時間,讓它給我們返回,我們一般會使用 delegate 或者 NSNotification。這樣的寫法沒什么問題,但是使用過 delegate 和 NSNotification 大家就應(yīng)該會感覺到——我們會不可避免的將代碼寫的到處都是,我們需要在某處開始一個任務(wù),在另外一個地方來處理這個返回結(jié)果。使用 Block 就可以在一定程度上避免這個問題。
如何使用 Block
下面這張圖片來自蘋果官方文檔:

Block 的聲明格式如下:
return_type (^block_name)(param_type, param_type, ...
^ 符號其實就是專門用來表示:我們在聲明一個 Block。
聲明舉例:
int (^add)(int,int)
block 的定義格式如下:
^return_type(param_type param_name, param_type param_name, ...)
{ ... return return_type; }
要注意,定義和聲明的格式有一些不同。定義以 ^ 開始,后面跟著參數(shù)(參數(shù)在這里一定要命名),順序和類型一定要和聲明中的順序一樣。定義時,返回值類型是 optional 的,我們可以在后面的代碼中確定返回值類型。如果有多個返回 statement,他們也只能有一個返回值類型,或者把他們轉(zhuǎn)成同一個類型。block 的定義舉例:
^(int number1, int number2){ return number1+number2 }
我們把聲明和定義放在一起:
int (^add)(int,int) = ^(int number1, int number2){ return number1+number2;}
調(diào)用的時候:
int resultFromBlock = add(2,2);
我們將使用 block 與不使用 block 做一些對比
舉例 :NSArray
普通 for 循環(huán):
BOOL stop;
for (int i = 0 ; i < [theArray count] ; i++) {
NSLog(@"The object at index %d is %@",i,[theArray objectAtIndex:i]);
if (stop)
break;
}
這個 BOOL stop 現(xiàn)在看上去有點奇怪,但看到后面 block 實現(xiàn)就能理解了
快速迭代:
BOOL stop;
int idx = 0;
for (id obj in theArray) {
NSLog(@"The object at index %d is %@",idx,obj);
if (stop) break;
idx++;
}
使用 block :
[theArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSLog(@"The object at index %d is %@",idx,obj);
}];
在上面的代碼中, BOOL stop 設(shè)置為 YES 的時候,可以從block 內(nèi)部停止下一步運行。
從上面三段代碼的對比中,我們可以至少可以看出 block 兩方面的優(yōu)勢:
- 簡化了代碼
- 提高了速度
舉例:UIView Animation
非 Block 實現(xiàn)
-(void)removeAnimationView:(id)sender {
[animatingView removeFromSuperview];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[UIView beginAnimations:@"Example" context:nil];
[UIView setAnimationDuration:5.0];
[UIView setAnimationDidStopSelector:@selector(removeAnimationView)];
[animatingView setAlpha:0];
[animatingView setCenter:CGPointMake(animatingView.center.x+50.0,
animatingView.center.y+50.0)];
[UIView commitAnimations];
}
block 實現(xiàn)
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[UIView animateWithDuration:5.0 animations:^{
[animatingView setAlpha:0];
[animatingView setCenter:CGPointMake(animatingView.center.x+50.0, animatingView.center.y+50.0)];
}
completion:^(BOOL finished) {
[animatingView removeFromSuperview];
}];
}
同樣我們可以看出 block 的優(yōu)勢:簡化了代碼
讓代碼保持在一起,不需要在一個地方開始動畫,在另一個地方回調(diào)。讀寫起來都比較方便。蘋果也建議這么做,不然蘋果用 block 重寫以前的代碼干嘛呢~
block 的應(yīng)用
1. enumerateObjectsUsingBlock
之前的代碼實例中已經(jīng)提到過,用來迭代數(shù)組十分方便,具體看下面的代碼實例:
-(NSArray*)retrieveInventoryItems {
// 1 - 聲明
NSMutableArray* inventory = [NSMutableArray new];
NSError* err = nil;
// 2 - 得到 inventory 數(shù)據(jù)
NSArray* jsonInventory = [NSJSONSerialization JSONObjectWithData:
[NSData dataWithContentsOfURL:[NSURL URLWithString:kInventoryAddress]]
options:kNilOptions
error:&err];
// 3 - 使用 block 遍歷
[jsonInventory enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSDictionary* item = obj;
[inventory addObject:[[IODItem alloc]
initWithName:[item objectForKey:@"Name"]
andPrice:[[item objectForKey:@"Price"] floatValue]
andPictureFile:[item objectForKey:@"Image"]]];
}];
// 4 - 返回一個 inventory 的 copy
return [inventory copy];
}
我們在上面的代碼中 3 處使用了 block:
使用了 enumerateObjectsUsingBlock 方法,把一個普通的 NSDictionary 轉(zhuǎn)化成一個 IODItem 類的對象。我們對一個JSON Array 對象發(fā)送 enumerateObjectsUsingBlock 消息,迭代這個 array,得到 item 字典,然后用這個字典得到 IODItem,最后把這些對象添加到 inventory 數(shù)組然后返回。
2.sortedArrayUsingComparator
enumerateObjectsUsingBlock我們上面已經(jīng)用過,主要來看 sortedArrayUsingComparator ,這個 block 以一個升序返回一個 array,這個升序由一個 NSComparator block 決定
注意:compare 方法的使用有點沒太明白,但是根據(jù) sortedArrayUsingComparator 是返回一個升序數(shù)組,所以compare 方法應(yīng)該是返回兩者之間更大的??
-(NSString*)orderDescription {
// 1 - 聲明
NSMutableString* orderDescription = [NSMutableString new];
// 2 - 使用 block 進行排序
NSArray* keys = [[self.orderItems allKeys] sortedArrayUsingComparator:
^NSComparisonResult(id obj1, id obj2) {
IODItem* item1 = (IODItem*)obj1;
IODItem* item2 = (IODItem*)obj2;
return [item1.name compare:item2.name];
}];
// 3 - 使用 block 遍歷
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
IODItem* item = (IODItem*)obj;
NSNumber* quantity = (NSNumber*)[self.orderItems objectForKey:item];
[orderDescription appendFormat:@"%@ x%@\n", item.name, quantity];
}];
// 4 - 返回
return [orderDescription copy];
}
注釋2:得到一個包含 dictionary 中所有 key 的數(shù)組,然后使用 sortedArrayUsingComparator 這個 block 方法,把這些所有的 key 按升序進行排序。
總結(jié)一些比較常用的 block
NSArray
- enumerateObjectsUsingBlock 這個是我最常使用的 block ,上面已經(jīng)介紹過了,用來迭代數(shù)組非常方便,個人認為這應(yīng)該是最好用的 block 了。
- enumerateObjectsAtIndexes:usingBlock: 和 enumerateObjectsUsingBlock 差不多,但是我們可以選擇只迭代數(shù)組的一部分,而不是迭代整個數(shù)組。這個需要迭代的范圍由 indexSet 參數(shù)傳入。
- indexesOfObjectsPassingTest 返回一個數(shù)組中,通過了特定的 test 的對象的 indexSet。用這個 block 來查找特定的數(shù)據(jù)很方便。
NSDictionary
- enumerateKeysAndObjectsUsingBlock 迭代整個字典,返回字典中所有的 key 和對應(yīng)的值(如果是想用這個 block 來代替 objectForKey 方法,確實有些多此一舉,但是如果你需要返回字典中全部的 key, value,這個 block 是一個很好的選擇)。
- keysOfEntriesPassingTest 和數(shù)組里的 indexesOfObjectsPassingTest block 類似,返回通過特定的 test 的一組對象的 key。
UIView Animation
animateWithDuration: animation: completion: 寫過動畫大家應(yīng)該還是比較了解的,動畫的 block 實現(xiàn)確實比非 block 實現(xiàn)簡單、方便了很多。
GCD
dispatch async:這時異步 GCD 的主要功能,在這里其實最重要的是要理解 block 是一個普通的對象,是可以作為參數(shù)傳遞給 dispatch queue 的。
使用我們自己的 block
除了使用這些系統(tǒng)提供給我們的 block,我們有時候自己寫的方法里也許也想要用到 block。我們來看一些簡單的示例代碼,這段代碼聲明了一個接收 block 作為參數(shù)的方法:
-(void)doMathWithBlock:(int (^)(int, int))mathBlock {
self.label.text = [NSString stringWithFormat:@"%d", mathBlock(3, 5)];
}
// 如何調(diào)用
-(IBAction)buttonTapped:(id)sender {
[self doMathWithBlock:^(int a, int b) {
return a + b;
}];
}
因為 block 就是一個 Objective-C 對象,所以我們可以把 block 存儲在一個 property 中以便之后調(diào)用。這種方式在處理異步任務(wù)的時候特別有用,我們可以在一個異步任務(wù)完成之后存儲一個 block,之后可以調(diào)用。下面是一段示例代碼:
@property (strong) int (^mathBlock)(int, int);
// 存儲 block 以便之后調(diào)用
-(void)doMathWithBlock:(int (^)(int, int))mathBlock {
self.mathBlock = mathBlock;
}
// 調(diào)用上面的方法,并傳入一個 block
-(IBAction)buttonTapped:(id)sender {
[self doMathWithBlock:^(int a, int b) {
return a + b;
}];
}
// 結(jié)果
-(IBAction)button2Tapped:(id)sender {
self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)];
}
還有,我們可以使用 typedef 來簡化block 語法,當(dāng)然效果和上面的是差不多的,我們來看下面的例子:
typedef int (^MathBlock)(int, int);
// 使用 tpyedef 聲明一個property
@property (strong) MathBlock mathBlock;
-(void)doMathWithBlock:(MathBlock) mathBlock {
self.mathBlock = mathBlock;
}
-(IBAction)buttonTapped:(id)sender {
[self doMathWithBlock:^(int a, int b) {
return a + b;
}];
}
-(IBAction)button2Tapped:(id)sender {
self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)];
}
Bingo !