2

什么是 block

Blocks 是 iOS 4.0 之后有的新功能。
Block 能够让我们的代码变得更简单,能够减少代码量,降低对于 delegate 的依赖,还能够提高代码的可读性。

本质上来说,一个 Block 就是一段能够在将来被执行的代码。本身 Block 就是一个普通的 Objective-C 对象。正因为它是对象,Block 可以被作为参数传递,可以作为返回值从一个方法返回,可以用来给变量赋值。
在其他语言(Python, Ruby, Lisp etc.)中,Block 被叫做闭包——因为他们在被声明的时候的封装状态。Block 为指向它内部的局部变量创造了一个常量 copy。

在 Block 之前,如果我们想要调用一段代码,然后之后一段时间,让它给我们返回,我们一般会使用 delegate 或者 NSNotification。这样的写法没什么问题,但是使用过 delegate 和 NSNotification 大家就应该会感觉到——我们会不可避免的将代码写的到处都是,我们需要在某处开始一个任务,在另外一个地方来处理这个返回结果。使用 Block 就可以在一定程度上避免这个问题。

如何使用 Block

下面这张图片来自苹果官方文档:
blocks.jpg

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; }

要注意,定义和声明的格式有一些不同。定义以 ^ 开始,后面跟着参数(参数在这里一定要命名),顺序和类型一定要和声明中的顺序一样。定义时,返回值类型是 optional 的,我们可以在后面的代码中确定返回值类型。如果有多个返回 statement,他们也只能有一个返回值类型,或者把他们转成同一个类型。
block 的定义举例:

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

我们把声明和定义放在一起:

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

调用的时候:

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 的文章。


Richard_Gao
429 声望7 粉丝