本文轉(zhuǎn)載于 http://segmentfault.com/a/1190000003093017, 如有侵權(quán)涉馁,請(qǐng)?jiān)c我溝通,可以立即取消發(fā)布峭火。
什么是Block
Blocks 是 iOS 4.0 之后有的新功能毁习。
Block 能夠讓我們的代碼變得更簡(jiǎn)單,能夠減少代碼量卖丸,降低對(duì)于 delegate 的依賴(lài)纺且,還能夠提高代碼的可讀性。
本質(zhì)上來(lái)說(shuō)稍浆,一個(gè) Block 就是一段能夠在將來(lái)被執(zhí)行的代碼载碌。本身 Block 就是一個(gè)普通的 Objective-C 對(duì)象。正因?yàn)樗菍?duì)象衅枫,Block 可以被作為參數(shù)傳遞嫁艇,可以作為返回值從一個(gè)方法返回,可以用來(lái)給變量賦值弦撩。
在其他語(yǔ)言(Python, Ruby, Lisp etc.)中步咪,Block 被叫做閉包——因?yàn)樗麄冊(cè)诒宦暶鞯臅r(shí)候的封裝狀態(tài)。Block 為指向它內(nèi)部的局部變量創(chuàng)造了一個(gè)常量 copy益楼。
在 Block 之前猾漫,如果我們想要調(diào)用一段代碼,然后之后一段時(shí)間感凤,讓它給我們返回悯周,我們一般會(huì)使用 delegate 或者 NSNotification。這樣的寫(xiě)法沒(méi)什么問(wèn)題俊扭,但是使用過(guò) delegate 和 NSNotification 大家就應(yīng)該會(huì)感覺(jué)到——我們會(huì)不可避免的將代碼寫(xiě)的到處都是,我們需要在某處開(kāi)始一個(gè)任務(wù)坠陈,在另外一個(gè)地方來(lái)處理這個(gè)返回結(jié)果萨惑。使用 Block 就可以在一定程度上避免這個(gè)問(wèn)題。
如何使用 Block
下面這張圖片來(lái)自蘋(píng)果官方文檔:
Block 的聲明格式如下:
return_type (^block_name)(param_type, param_type, ...
^
符號(hào)其實(shí)就是專(zhuān)門(mén)用來(lái)表示:我們?cè)诼暶饕粋€(gè) Block仇矾。
聲明舉例:
int (^add)(int,int)
block 的定義格式如下:
^return_type(param_type param_name, param_type param_name, ...)
{ ... return return_type; }
要注意庸蔼,定義和聲明的格式有一些不同。定義以 ^ 開(kāi)始贮匕,后面跟著參數(shù)(參數(shù)在這里一定要命名)姐仅,順序和類(lèi)型一定要和聲明中的順序一樣。定義時(shí),返回值類(lèi)型是 optional 的掏膏,我們可以在后面的代碼中確定返回值類(lèi)型劳翰。如果有多個(gè)返回 statement,他們也只能有一個(gè)返回值類(lèi)型馒疹,或者把他們轉(zhuǎn)成同一個(gè)類(lèi)型佳簸。block 的定義舉例:
^(int number1, int number2){ return number1+number2 }
我們把聲明和定義放在一起:
int (^add)(int,int) = ^(int number1, int number2){ return number1+number2;}
調(diào)用的時(shí)候:
int resultFromBlock = add(2,2);
我們將使用 block 與不使用 block 做一些對(duì)比
舉例 :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;
}
這個(gè) BOOL stop 現(xiàn)在看上去有點(diǎn)奇怪,但看到后面 block 實(shí)現(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 的時(shí)候生均,可以從block 內(nèi)部停止下一步運(yùn)行。
從上面三段代碼的對(duì)比中腥刹,我們可以至少可以看出 block 兩方面的優(yōu)勢(shì):
- 簡(jiǎn)化了代碼
- 提高了速度
舉例:UIView Animation
非 Block 實(shí)現(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 實(shí)現(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)勢(shì):簡(jiǎn)化了代碼
讓代碼保持在一起马胧,不需要在一個(gè)地方開(kāi)始動(dòng)畫(huà),在另一個(gè)地方回調(diào)衔峰。讀寫(xiě)起來(lái)都比較方便佩脊。蘋(píng)果也建議這么做,不然蘋(píng)果用 block 重寫(xiě)以前的代碼干嘛呢~
block 的應(yīng)用
1. enumerateObjectsUsingBlock
之前的代碼實(shí)例中已經(jīng)提到過(guò)朽色,用來(lái)迭代數(shù)組十分方便邻吞,具體看下面的代碼實(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 - 返回一個(gè) inventory 的 copy
return [inventory copy];
}
我們?cè)谏厦娴拇a中 3 處使用了 block:
使用了 enumerateObjectsUsingBlock 方法,把一個(gè)普通的 NSDictionary 轉(zhuǎn)化成一個(gè) IODItem 類(lèi)的對(duì)象葫男。我們對(duì)一個(gè)JSON Array 對(duì)象發(fā)送 enumerateObjectsUsingBlock 消息抱冷,迭代這個(gè) array,得到 item 字典梢褐,然后用這個(gè)字典得到 IODItem旺遮,最后把這些對(duì)象添加到 inventory 數(shù)組然后返回。
2.sortedArrayUsingComparator
enumerateObjectsUsingBlock我們上面已經(jīng)用過(guò)盈咳,主要來(lái)看 sortedArrayUsingComparator 耿眉,這個(gè) block 以一個(gè)升序返回一個(gè) array,這個(gè)升序由一個(gè) NSComparator block 決定
注意:compare 方法的使用有點(diǎn)沒(méi)太明白鱼响,但是根據(jù) sortedArrayUsingComparator 是返回一個(gè)升序數(shù)組鸣剪,所以compare 方法應(yīng)該是返回兩者之間更大的?丈积?
-(NSString*)orderDescription {
// 1 - 聲明
NSMutableString* orderDescription = [NSMutableString new];
// 2 - 使用 block 進(jìn)行排序
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:得到一個(gè)包含 dictionary 中所有 key 的數(shù)組筐骇,然后使用 sortedArrayUsingComparator 這個(gè) block 方法,把這些所有的 key 按升序進(jìn)行排序江滨。
總結(jié)一些比較常用的 block
NSArray
- enumerateObjectsUsingBlock 這個(gè)是我最常使用的 block 铛纬,上面已經(jīng)介紹過(guò)了,用來(lái)迭代數(shù)組非常方便唬滑,個(gè)人認(rèn)為這應(yīng)該是最好用的 block 了告唆。
- enumerateObjectsAtIndexes:usingBlock: 和 enumerateObjectsUsingBlock 差不多棺弊,但是我們可以選擇只迭代數(shù)組的一部分,而不是迭代整個(gè)數(shù)組擒悬。這個(gè)需要迭代的范圍由 indexSet 參數(shù)傳入模她。
- indexesOfObjectsPassingTest 返回一個(gè)數(shù)組中,通過(guò)了特定的 test 的對(duì)象的 indexSet茄螃。用這個(gè) block 來(lái)查找特定的數(shù)據(jù)很方便缝驳。
NSDictionary
- enumerateKeysAndObjectsUsingBlock 迭代整個(gè)字典,返回字典中所有的 key 和對(duì)應(yīng)的值(如果是想用這個(gè) block 來(lái)代替 objectForKey 方法归苍,確實(shí)有些多此一舉,但是如果你需要返回字典中全部的 key, value用狱,這個(gè) block 是一個(gè)很好的選擇)。
- keysOfEntriesPassingTest 和數(shù)組里的 indexesOfObjectsPassingTest block 類(lèi)似拼弃,返回通過(guò)特定的 test 的一組對(duì)象的 key夏伊。
UIView Animation
animateWithDuration: animation: completion: 寫(xiě)過(guò)動(dòng)畫(huà)大家應(yīng)該還是比較了解的,動(dòng)畫(huà)的 block 實(shí)現(xiàn)確實(shí)比非 block 實(shí)現(xiàn)簡(jiǎn)單吻氧、方便了很多溺忧。
GCD
dispatch async:這時(shí)異步 GCD 的主要功能,在這里其實(shí)最重要的是要理解 block 是一個(gè)普通的對(duì)象盯孙,是可以作為參數(shù)傳遞給 dispatch queue 的鲁森。
使用我們自己的 block
除了使用這些系統(tǒng)提供給我們的 block,我們有時(shí)候自己寫(xiě)的方法里也許也想要用到 block振惰。我們來(lái)看一些簡(jiǎn)單的示例代碼歌溉,這段代碼聲明了一個(gè)接收 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;
}];
}
因?yàn)?block 就是一個(gè) Objective-C 對(duì)象,所以我們可以把 block 存儲(chǔ)在一個(gè) property 中以便之后調(diào)用骑晶。這種方式在處理異步任務(wù)的時(shí)候特別有用痛垛,我們可以在一個(gè)異步任務(wù)完成之后存儲(chǔ)一個(gè) block,之后可以調(diào)用桶蛔。下面是一段示例代碼:
@property (strong) int (^mathBlock)(int, int);
// 存儲(chǔ) block 以便之后調(diào)用
-(void)doMathWithBlock:(int (^)(int, int))mathBlock {
self.mathBlock = mathBlock;
}
// 調(diào)用上面的方法匙头,并傳入一個(gè) 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 來(lái)簡(jiǎn)化block 語(yǔ)法仔雷,當(dāng)然效果和上面的是差不多的蹂析,我們來(lái)看下面的例子:
typedef int (^MathBlock)(int, int);
// 使用 tpyedef 聲明一個(gè)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 !