iOS 對Block深入了解和應用

世事洞明皆學問湾笛,人情練達即文章楣导。
腹有詩書氣自華,人通國學身自重疆前。

什么是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實例.png

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

  1. enumerateObjectsUsingBlock 這個是我最常使用的 block ,上面已經(jīng)介紹過了米同,用來迭代數(shù)組非常方便骇扇,個人認為這應該是最好用的 block 了。
  2. enumerateObjectsAtIndexes:usingBlock: 和 enumerateObjectsUsingBlock 差不多面粮,但是我們可以選擇只迭代數(shù)組的一部分少孝,而不是迭代整個數(shù)組。這個需要迭代的范圍由 indexSet 參數(shù)傳入熬苍。
  3. indexesOfObjectsPassingTest 返回一個數(shù)組中稍走,通過了特定的 test 的對象的 indexSet。用這個block 來查找特定的數(shù)據(jù)很方便柴底。

NSDictionary

  1. enumerateKeysAndObjectsUsingBlock 迭代整個字典婿脸,返回字典中所有的 key 和對應的值(如果是想用這個 block 來代替 objectForKey 方法,確實有些多此一舉,但是如果你需要返回字典中全部的 key, value柄驻,這個 block 是一個很好的選擇)狐树。
  2. 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種

  1. 你可以引用 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)部直接修改該變量莉御。

  1. 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ū)討論艾少,不勝感激,共同成長翼悴。

部分資料來源于官方文檔
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Blocks/Articles/bxGettingStarted.html#//apple_ref/doc/uid/TP40007502-CH7-SW1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缚够,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谍椅,老刑警劉巖误堡,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異雏吭,居然都是意外死亡锁施,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門思恐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人膊毁,你說我怎么就攤上這事胀莹。” “怎么了婚温?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵描焰,是天一觀的道長。 經(jīng)常有香客問我栅螟,道長荆秦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任力图,我火速辦了婚禮步绸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吃媒。我一直安慰自己瓤介,他們只是感情好,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布赘那。 她就那樣靜靜地躺著刑桑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪募舟。 梳的紋絲不亂的頭發(fā)上祠斧,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機與錄音拱礁,去河邊找鬼琢锋。 笑死,一個胖子當著我的面吹牛呢灶,可吹牛的內(nèi)容都是我干的吩蔑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼填抬,長吁一口氣:“原來是場噩夢啊……” “哼烛芬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赘娄,失蹤者是張志新(化名)和其女友劉穎仆潮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遣臼,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡性置,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了揍堰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹏浅。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡庞瘸,死狀恐怖稠腊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吏颖,我是刑警寧澤蝙眶,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布季希,位于F島的核電站,受9級特大地震影響幽纷,放射性物質(zhì)發(fā)生泄漏式塌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一友浸、第九天 我趴在偏房一處隱蔽的房頂上張望峰尝。 院中可真熱鬧,春花似錦收恢、人聲如沸境析。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽劳淆。三九已至,卻和暖如春默赂,著一層夾襖步出監(jiān)牢的瞬間沛鸵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工缆八, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曲掰,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓奈辰,卻偏偏與公主長得像栏妖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子奖恰,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容