什么是 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
下面这张图片来自苹果官方文档:
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
- enumerateObjectsUsingBlock 这个是我最常使用的 block ,上面已经介绍过了,用来迭代数组非常方便,个人认为这应该是最好用的 block 了。
- enumerateObjectsAtIndexes:usingBlock: 和 enumerateObjectsUsingBlock 差不多,但是我们可以选择只迭代数组的一部分,而不是迭代整个数组。这个需要迭代的范围由 indexSet 参数传入。
- indexesOfObjectsPassingTest 返回一个数组中,通过了特定的 test 的对象的 indexSet。用这个 block 来查找特定的数据很方便。
NSDictionary
- enumerateKeysAndObjectsUsingBlock 迭代整个字典,返回字典中所有的 key 和对应的值(如果是想用这个 block 来代替 objectForKey 方法,确实有些多此一举,但是如果你需要返回字典中全部的 key, value,这个 block 是一个很好的选择)。
- 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 的文章。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。