世事洞明皆學問湾笛,人情練達即文章楣导。
腹有詩書氣自華,人通國學身自重疆前。
什么是Block
Block 是C語言的擴充功能寒跳,在 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 大家就應該會感覺到——我們會不可避免的將代碼寫的到處都是,我們需要在某處開始一個任務绢要,在另外一個地方來處理這個返回結(jié)果吏恭。使用 Block 就可以在一定程度上避免這個問題。
如何使用 Block
通過^操作符來聲明一個快變量重罪,塊體本身包含在{}體中樱哼。
Block 的聲明格式如下:
return_type(^block_name)(param_type,param_type,...)
^
符號其實就是專門用來表示:我們在聲明一個 Block。
聲明舉例:
int (^myBlock)(int, int)
block 的定義格式如下:
^(param_type param_name, param_type param_name,...){
...
return return_value
}
要注意蛆封,定義和聲明的格式有一些不同唇礁。定義以 ^ 開始勾栗,后面跟著參數(shù)(參數(shù)在這里一定要命名)惨篱,順序和類型一定要和聲明中的順序一樣。定義時围俘,返回值類型是 optional 的砸讳,我們可以在后面的代碼中確定返回值類型琢融。如果有多個返回 statement,他們也只能有一個返回值類型簿寂,或者把他們轉(zhuǎn)成同一個類型漾抬。block 的定義舉例:
^(int num1, int num2) {return num1 + num2};
我們把聲明和定義放在一起:
int (^myBlock)(int, int) = ^(int num1, float num2){
return num1 + num2;
};
Block 調(diào)用:
如果將一個Block聲明為一個變量,你就可以像使用函數(shù)一樣使用它常遂。
int resultFromBlock = myBolck(10, 20);
下面是一個完整的實例:
int multiplier = 7;
int (^myBolck)(int) = ^(int num){
return num * multiplier;
}
NSlog("%d",myBlock(3)); //result value 21
Block的應用解析
直接使用塊纳令,快是內(nèi)聯(lián)代碼的集合。
1. enumerateObjectsUsingBlock
-(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. enumerateObjectsUsingBlock內(nèi)部模擬實現(xiàn)
你是否思考過它的內(nèi)部實現(xiàn)原理性湿,但由于objective-c不開源原因,我們只能來根據(jù)Block的原理來模擬它的實現(xiàn)满败,那么讓我們來重寫一個它的內(nèi)部實現(xiàn),通過NSArray的分類給外界提供一個測試方法肤频,來模擬它的內(nèi)部實現(xiàn)。
//分類.h文件
- (void)enumTestBlock:(void(^)(id obj,NSUInteger,BOOL *))block;
//分類.m文件
- (void)enumTestBlock:(void(^)(id obj,NSUInteger,BOOL *))enumBlock{
if (!enumBlock && self.count == 0) {
return;
}
BOOL stop = NO;
for (int index = 0; index < self.count; index ++) {
if (!stop) {
enumBlock(self[index],index,&stop);
}else{
break;
}
}
}
//外部調(diào)用
[@[@"1",@"2",@"3"] enumTestBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isEqualToString:@"2"]) {
NSLog(@"index %lu",(unsigned long)idx);
*stop = YES;
}
}];
//index ---
這里唯一需要注意就是Block內(nèi)傳入的一個bool類型的指針參數(shù)BOOL *stop,是為了外部能在同一內(nèi)存地址上修改內(nèi)部實現(xiàn)中的變量stop的值葫录,來實現(xiàn)跳出循環(huán)的功能着裹。
總結(jié)一些比較常用的 block
NSArray
- enumerateObjectsUsingBlock 這個是我最常使用的 block ,上面已經(jīng)介紹過了米同,用來迭代數(shù)組非常方便骇扇,個人認為這應該是最好用的 block 了。
- enumerateObjectsAtIndexes:usingBlock: 和 enumerateObjectsUsingBlock 差不多面粮,但是我們可以選擇只迭代數(shù)組的一部分少孝,而不是迭代整個數(shù)組。這個需要迭代的范圍由 indexSet 參數(shù)傳入熬苍。
- indexesOfObjectsPassingTest 返回一個數(shù)組中稍走,通過了特定的 test 的對象的 indexSet。用這個block 來查找特定的數(shù)據(jù)很方便柴底。
NSDictionary
- enumerateKeysAndObjectsUsingBlock 迭代整個字典婿脸,返回字典中所有的 key 和對應的值(如果是想用這個 block 來代替 objectForKey 方法,確實有些多此一舉,但是如果你需要返回字典中全部的 key, value柄驻,這個 block 是一個很好的選擇)狐树。
- keysOfEntriesPassingTest 和數(shù)組里的 indexesOfObjectsPassingTest block 類似,返回通過特定的 test 的一組對象的 key鸿脓。
UIView Animation
animateWithDuration: animation: completion: 寫過動畫大家應該還是比較了解的抑钟,動畫的 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)用揪利。這種方式在處理異步任務的時候特別有用,我們可以在一個異步任務完成之后存儲一個 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 語法甜刻,當然效果和上面的是差不多的,我們來看下面的例子:
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)];
}
我們將使用 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 設置為 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和變量之間的交互
在塊對象的代碼體中赡麦,變量可以用五種不同的方式處理即變量分為5種
- 你可以引用 3 種標準類型的變量朴皆,就像你在普通方法中使用的那樣子:
(1)全局變量,包括static修飾過的靜態(tài)變量泛粹,可讀寫遂铡。
(2)全局方法,技術(shù)上來說不能稱為變量晶姊。
(3)局部變量和從上下文帶進來的參數(shù)扒接,僅可讀。
block可以修改全局變量,是因為全局變量放在堆區(qū)珠增,局部變量在棧區(qū),所以不能修改砍艾,加上__block之后蒂教,相當于加了個標識位,遇到__block就把內(nèi)存由棧區(qū)放在堆區(qū)脆荷。
Block不允許修改外部變量的值凝垛。Apple這樣設計,應該是考慮到了block的特殊性蜓谋,block也屬于“函數(shù)”的范疇梦皮,變量進入block,實際就是已經(jīng)改變了作用域桃焕。在幾個作用域之間進行切換時剑肯,如果不加上這樣的限制,變量的可維護性將大大降低观堂。又比如我想在block內(nèi)聲明了一個與外部同名的變量让网,此時是允許呢還是不允許呢?只有加上了這樣的限制师痕,這樣的情景才能實現(xiàn)溃睹。于是棧區(qū)變成了紅燈區(qū),堆區(qū)變成了綠燈區(qū)胰坟。
Block不允許修改外部變量的值因篇,這里所說的外部變量的值,指的是棧中指針的內(nèi)存地址笔横。__block 所起到的作用就是只要觀察到該變量被 block 所持有竞滓,就將“外部變量”在棧中的內(nèi)存地址放到了堆中。進而在block內(nèi)部也可以修改外部變量的值吹缔。
為什么從棧到堆就可以修改了呢虽界?
Block默認的是NSGlobalBlock類似于函數(shù),存放在代碼段;當block內(nèi)部使用了外部的變量時涛菠,block的存放位置變成了NSMallockBlock(堆),所以用__block修飾后才可以在block內(nèi)部直接修改該變量莉御。
- Block也支持另外兩種類型的變量:
(4)函數(shù)級別上的 __block 修飾的對象。它在block里面是可以修改的俗冻,如果這個 block 被 copy 到了棧區(qū)礁叔,這個對象就會被強引用。
(5)const引入的迄薄。
最終琅关,在一個方法的實現(xiàn)當中,blocks 也許會強引用 Objective-C 實例變量。
以下規(guī)則適用于在 block 中使用的變量
(1)可以接收全局變量涣易,包括存在于上下文中的靜態(tài)變量画机。
(2)傳遞到block中的變量(就像函數(shù)傳遞參數(shù)一樣)
(3)相對于 block 塊的非靜態(tài)堆區(qū)對象被識別為 const 對象。他們的值會以指針的形式傳遞到 block 中新症。
(4)__block 修飾的對象允許在 block 中進行修改步氏,并會被 block 強引用。
(5)在 block 塊中實例化的對象徒爹,與在函數(shù)中實例化的對象基本一致荚醒。每一次調(diào)用這個 block 都會提供一個變量的 copy。相應的隆嗅,這些對象可以被當做 const 或者是強引用的對象使用界阁。
__block 存儲類型(塊存儲類型)
您可以通過應用_block塊存儲類型修飾符來指定導入的變量是可變的(即讀-寫)。塊存儲與寄存器胖喳、自動存儲和本地變量的靜態(tài)存儲類型相似泡躯,但相互排斥。
__block 變量在一個容器中存活丽焊,可以被變量的上下文共享精续,可以被所有 block 共享,可以被 copy 修飾過的 block 共享粹懒,以及在 block 塊中創(chuàng)建的對象共享重付。也就是說,如果有任何 copy 出來的 block 用了這個變量凫乖,它會一直存活于堆區(qū)當中确垫。
作為一個優(yōu)化,block 存儲開始與堆區(qū)帽芽,就像 blocks 他們自己做的那樣子删掀。如果這個 block 被 copy 了(或者在 OC 當中 block 接收到了 copy 消息)。變量就會被復制到棧區(qū)去导街。也就是說披泪,這個變量可以一直被修改了。
對于 __block 變量有著兩點限制:他們不能用于可變長度的數(shù)組搬瑰,也不能包括C99中可變長度數(shù)組的結(jié)構(gòu)體款票。
__block int x = 123; //x lives in block storage
void (^logXAndY)(int) = ^(int Y){
x = x + y;
NSlog(@"%d %d",x,y);
}
logXAndY(456);//x new is 579 y = 456
小結(jié):此文持續(xù)更新和維護中,如中有不正確的地方泽论,歡迎各路大神指導或在下方評論區(qū)討論艾少,不勝感激,共同成長翼悴。