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 的声明格式如下:
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 两方面的优势:
非 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 重写以前的代码干嘛呢~
之前的代码实例中已经提到过,用来迭代数组十分方便,具体看下面的代码实例:
-(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 数组然后返回。
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 按升序进行排序。
这个方法苹果的官方说明:将一个 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。
animateWithDuration: animation: completion: 写过动画大家应该还是比较了解的,动画的 block 实现确实比非 block 实现简单、方便了很多。
dispatch async:这时异步 GCD 的主要功能,在这里其实最重要的是要理解 block 是一个普通的对象,是可以作为参数传递给 dispatch queue 的。
除了使用这些系统提供给我们的 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 的文章。