iOS 如何使用 Block

什麼是 block

Blocks 是 iOS 4.0 以後有的新功能。
Block 可以讓咱們的代碼變得更簡單,可以減小代碼量,下降對於 delegate 的依賴,還可以提升代碼的可讀性。ios

本質上來講,一個 Block 就是一段可以在未來被執行的代碼。自己 Block 就是一個普通的 Objective-C 對象。正由於它是對象,Block 能夠被做爲參數傳遞,能夠做爲返回值從一個方法返回,能夠用來給變量賦值。
在其餘語言(Python, Ruby, Lisp etc.)中,Block 被叫作閉包——由於他們在被聲明的時候的封裝狀態。Block 爲指向它內部的局部變量創造了一個常量 copy。json

在 Block 以前,若是咱們想要調用一段代碼,而後以後一段時間,讓它給咱們返回,咱們通常會使用 delegate 或者 NSNotification。這樣的寫法沒什麼問題,可是使用過 delegate 和 NSNotification 你們就應該會感受到——咱們會不可避免的將代碼寫的處處都是,咱們須要在某處開始一個任務,在另一個地方來處理這個返回結果。使用 Block 就能夠在必定程度上避免這個問題。數組

如何使用 Block

下面這張圖片來自蘋果官方文檔:
blocks.jpg閉包

Block 的聲明格式以下:app

return_type (^block_name)(param_type, param_type, ...

^ 符號其實就是專門用來表示:咱們在聲明一個 Block。
聲明舉例:異步

int (^add)(int,int)

block 的定義格式以下:async

^return_type(param_type param_name, param_type param_name, ...) { ... return return_type; }

要注意,定義和聲明的格式有一些不一樣。定義以 ^ 開始,後面跟着參數(參數在這裏必定要命名),順序和類型必定要和聲明中的順序同樣。定義時,返回值類型是 optional 的,咱們能夠在後面的代碼中肯定返回值類型。若是有多個返回 statement,他們也只能有一個返回值類型,或者把他們轉成同一個類型。
block 的定義舉例:動畫

^(int number1, int number2){ return number1+number2 }

咱們把聲明和定義放在一塊兒:spa

int (^add)(int,int) = ^(int number1, int number2){ 
                            return number1+number2;
}

調用的時候:3d

int resultFromBlock = add(2,2);

咱們將使用 block 與不使用 block 作一些對比

舉例 :NSArray
普通 for 循環:

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 如今看上去有點奇怪,但看到後面 block 實現就能理解了

快速迭代:

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 內部中止下一步運行。
從上面三段代碼的對比中,咱們能夠至少能夠看出 block 兩方面的優點:

  • 簡化了代碼
  • 提升了速度

舉例:UIview Animation

非 Block 實現

-(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 實現

-(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 的優點:簡化了代碼

讓代碼保持在一塊兒,不須要在一個地方開始動畫,在另外一個地方回調。讀寫起來都比較方便。
蘋果也建議這麼作,否則蘋果用 block 重寫之前的代碼幹嗎呢~

block 的應用

1. enumerateObjectsUsingBlock

以前的代碼實例中已經提到過,用來迭代數組十分方便,具體看下面的代碼實例:

-(NSArray*)retrieveInventoryItems {
    // 1 - 聲明
    NSMutableArray* inventory = [NSMutableArray new];
    NSError* err = nil;
    // 2 - 獲得 inventory 數據
    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 轉化成一個 IODItem 類的對象。咱們對一個JSON Array 對象發送 enumerateObjectsUsingBlock 消息,迭代這個 array,獲得 item 字典,而後用這個字典獲得 IODItem,最後把這些對象添加到 inventory 數組而後返回。

2.sortedArrayUsingComparator

enumerateObjectsUsingBlock咱們上面已經用過,主要來看 sortedArrayUsingComparator ,這個 block 以一個升序返回一個 array,這個升序由一個 NSComparator block 決定

注意:compare 方法的使用有點沒太明白,可是根據 sortedArrayUsingComparator 是返回一個升序數組,因此compare 方法應該是返回二者之間更大的??

-(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 的數組,而後使用 sortedArrayUsingComparator 這個 block 方法,把這些全部的 key 按升序進行排序。

3.enumerateKeysAndObjectsUsingBlock

這個方法蘋果的官方說明:將一個 block 對象使用在一個字典的 entries 上(Apply an given block object to the entries of dictionary),咱們來看一段實例代碼:

-(float)totalOrder {
        // 1 - 定義
    __block float total = 0.0;
        // 2 - 使用 block 計算
    float (^itemTotal)(float,int) = ^float(float price, int quantity) {
        return price * quantity;
    };
        // 3 - 使用 block 遍歷 dictionary
    [self.orderItems enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        IODItem* item = (IODItem*)key;
        NSNumber* quantity = (NSNumber*)obj;
        int intQuantity = [quantity intValue];
        total += itemTotal(item.price, intQuantity);
    }];
        // 4 - 返回
    return total;
}

註釋1:須要注意的是 block,由於咱們須要在 block 內部使用這個 total 變量;若是咱們不使用 block 關鍵字,下面的 block 會建立一個這個 total 變量的常量拷貝(const copy),當指向 block 內部的時候,就使用這個拷貝。這意味着在 block 咱們沒法修改它的值。但添加了這個關鍵字咱們就能夠從 block 內部讀取變量的值,或者把值寫入 block 內的變量。

註釋3:使用 enumerateKeysAndObjectsUsingBlock 能夠遍歷獲得字典裏全部的對象,以及它們對應的 key。

總結一些比較經常使用的 block

NSArray

  1. enumerateObjectsUsingBlock 這個是我最常使用的 block ,上面已經介紹過了,用來迭代數組很是方便,我的認爲這應該是最好用的 block 了。
  2. enumerateObjectsAtIndexes:usingBlock: 和 enumerateObjectsUsingBlock 差很少,可是咱們能夠選擇只迭代數組的一部分,而不是迭代整個數組。這個須要迭代的範圍由 indexSet 參數傳入。
  3. indexesOfObjectsPassingTest 返回一個數組中,經過了特定的 test 的對象的 indexSet。用這個 block 來查找特定的數據很方便。

NSDictionary

  1. enumerateKeysAndObjectsUsingBlock 迭代整個字典,返回字典中全部的 key 和對應的值(若是是想用這個 block 來代替 objectForKey 方法,確實有些畫蛇添足,可是若是你須要返回字典中所有的 key, value,這個 block 是一個很好的選擇)。
  2. keysOfEntriesPassingTest 和數組裏的 indexesOfObjectsPassingTest block 相似,返回經過特定的 test 的一組對象的 key。

UIView Animation

animateWithDuration: animation: completion: 寫過動畫你們應該仍是比較瞭解的,動畫的 block 實現確實比非 block 實現簡單、方便了不少。

GCD

dispatch async:這時異步 GCD 的主要功能,在這裏其實最重要的是要理解 block 是一個普通的對象,是能夠做爲參數傳遞給 dispatch queue 的。

使用咱們本身的 block

除了使用這些系統提供給咱們的 block,咱們有時候本身寫的方法裏也許也想要用到 block。咱們來看一些簡單的示例代碼,這段代碼聲明瞭一個接收 block 做爲參數的方法:

-(void)doMathWithBlock:(int (^)(int, int))mathBlock {
    self.label.text = [NSString stringWithFormat:@"%d", mathBlock(3, 5)];
}
 
// 如何調用
-(IBAction)buttonTapped:(id)sender {
    [self doMathWithBlock:^(int a, int b) {
        return a + b;
    }];
}

由於 block 就是一個 Objective-C 對象,因此咱們能夠把 block 存儲在一個 property 中以便以後調用。這種方式在處理異步任務的時候特別有用,咱們能夠在一個異步任務完成以後存儲一個 block,以後能夠調用。下面是一段示例代碼:

@property (strong) int (^mathBlock)(int, int);

// 存儲 block 以便以後調用
-(void)doMathWithBlock:(int (^)(int, int))mathBlock {
    self.mathBlock = mathBlock;
}
 
// 調用上面的方法,並傳入一個 block
-(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)];
}

還有,咱們可使用 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)];
}

參考連接
Bingo !

關於 block 的使用說的差很少了,其實關於 block 最關鍵的是要理解,block 是能夠做爲參數和返回值使用得。接下來會總結一篇關於 GCD 的文章。

相關文章
相關標籤/搜索