(转)在iOS4中使用代码块-基础知识(翻译)
iOS4引入了一个新特性,支持代码块的使用,这将从根本上改变你的编程方式。代码块是对C语言的一个扩展,因此在Objective-C中完全支持。如果你学过Ruby,Python或Lisp编程语言,那么你肯定知道代码块的强大之处。简单的说,你可以通过代码块封装一组代码语句并将其当作一个对象。代码块的使用是一种新的编码风格,可以让你运用自如的使用iOS4中新增API。
我们先来看两个在iOS4中使用代码块的例子(你很有可能已经见过):view animations 和enumeration
使用代码块的例子
第一个例子,假设我们创建一个纸牌游戏,需要展现纸牌被派发到玩家面前的动画效果。幸运的是通过UIKit框架可以很容易的实现一个动画效果。但是最终是什么样的动画是由你的程序决定的。你可以在代码块中指定动画的内容然后再将代码块传给animateWithDuration:animations:方法,像下面这样:
[UIView animateWithDuration:2.0
animations:^ {
self.cardView.alpha = 1.0;
self.cardView.frame = CGRectMake(176.0, 258.0, 72.0, 96.0);
self.cardView.transform = CGAffineTransformMakeRotation(M_PI);
}
];
当这个动画代码块执行时,我们的纸牌会展现三种方式的动画:改变它的alpha值从而淡入显示,改变它的位置到右下角(玩家的位置),以及自转180度(为了使其效果更好)。
第二个代码块的例子是迭代一个纸牌的集合,并打印其名字和在集合里的索引值。
你可以通过使用for循环来达到目的,但是在iOS4中NSArray类有一个使用了代码块的方便方法:enumerateObjectsUsingBlock:。下面是如何使用它:
NSArray *cards = [NSArray arrayWithObjects:@"Jack", @"Queen", @"King", @"Ace", nil];
[cards enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
NSLog(@"%@ card at index %d", object, index);
}];
这个代码块使用了三个参数:数组中的一个对象,该对象的索引,以及一个标识迭代是否结束的标志。我们稍候再对其进一步探讨。enumerateObjectsUsingBlock: 这个方法会将集合中的每一个元素传入相应的参数并调用代码块中的方法。
因此在你的mac和iOS程序中使用代码块的优势是:它允许你附加任意的代码到苹果官方提供的方法上。尽管在概念上与代理相似,但是在方法中使用简短的内联代码块往往更加方便,更加优雅。
这是一个好的开始,但重要的是要明白它内部的处理。当我学习新东西的时候,我喜欢先将其分为一个个简单的部分,了解它们如何工作,然后再将它们组装到一块,这样我会对自己写的代码以及快速解决出现的问题充满信心。因此,让我们先回头学习下如何声明和调用简单的代码块。
代码块的基本概念
一个代码块可以简单看作是一组可执行的代码。例如,下面是一个打印当前日期和时间的代码块:
^ {
NSDate *date = [NSDate date];
NSLog(@"The date and time is %@", date);
};
插入符号(^)声明一个代码块的开始,一对大括号{}构成了代码块的体部。你可以认为代码块与一个匿名函数类似。那么,如果是一个匿名的函数,我们该怎么调用这个代码块呢?最常见使用代码块的方式是将其传入方法中供方法回调,就像之前我们已经见到了view animations 和enumeration。另一种使用代码块的方式是将其赋予代码块变量,然后可使用该变量来直接调用代码块。以下是如何声明我们的代码块并将它赋予代码块变量now:
void (^now)(void) = ^ {
NSDate *date = [NSDate date];
NSLog(@"The date and time is %@", date);
};
声明一个块变量的语法需要一些时间适应,这才有趣。如果你使用过函数指针,代码块变量与其类似。在上面代码等号右边是我们已经介绍过的代码块。等号左边我们声明了一个代码块变量now。
[attachment=25059]
代码块变量之前有^符号并被小括号包着,代码块变量有类型定义的。因此,上图中的now变量可以应用任何无参,无返回值的代码块。我们之前声明的代码块符合这要求,,所以我们可以放心的把它分配给now变量。
只要有一个代码块变量,并在其作用域范围内,我们就可以像调用函数一样来调用它。下面是如何调用我们的代码块:
now();
你可以在C函数或者Objective-c方法中声明代码块变量,然后在同一作用域内调用它,就像我们前面说明那样。当代码块执行时,它打印当前的日期和时间。目前为止,进展顺利。
代码块是闭包
如果这就是代码块的全部的话,那么他与函数是完全相同的。但事实是代码块不仅仅是一组可执行的代码。代码块能够捕捉到已声明的同一作用域内的变量,同时由于代码块是闭包,在代码块声明时就将使用的变量包含到了代码块范围内。为了说明这一点,让我们改变一下前面的例子,将日期的初始化移到代码块之外。
NSDate *date = [NSDate date];
void (^now)(void) = ^ {
NSLog(@"The date and time is %@", date);
};
now();
当你第一次调用这个代码块的时候,它与我们之前的版本结果完全一致:打印当前的日期和时间。但是当我们改变日期后再调用代码块,那么就会有显著的不同了,
sleep(5);
date = [NSDate date];
now();
尽管我们在调用代码块之前改变了日期,但是当代码块调用时仍然打印的是之前的日期和时间。就像是日期在代码块声明时停顿了一样。为什么会这样呢,当程序执行到代码块的声明时,代码块对同一作用域并且块内用到的变量做一个只读的备份。你可以认为变量在代码块内被冻结了。因此,不论何时当代码块被调用时,立即调用或5秒钟之后,只要在程序退出之前,它都是打印最初的日期和时间。
事实上,上面那个展示代码块是闭包的例子并不十分完善,毕竟,你可以将日期作为一个参数传入到代码块中(下面讲解)。但是当你将代码块在不同方法间传递时闭包的特性就会变得十分有用,因为它里面的变量是保持不变的。
代码块参数
就像函数一样,代码块可以传入参数和返回结果。例如,我们想要一个能够返回指定数的三倍的代码块,下面是实现的代码块:
^(int number) {
return number * 3;
};
为代码块声明一个变量triple,如下:
int (^triple)(int) = ^(int number) {
return number * 3;
};
上面说过,我们需要熟悉等号左边声明代码块变量的语法。现在让我们从左到右分开来说明:
[attachment=25060]
最左边的int是返回值类型,中间是小括号包围插入符号^及代码块变量的名字,最后又一个小括号,包围着参数的类型(上面例子中只有一个int参数)。等号右边的代码块声明必须符合左侧的定义。有一点要说明的是,为了方便,可以不声明代码块的返回类型,编译器会从返回语句中做出判断。
要调用这个代码块,你需要传入一个需要乘3的参数,并接受返回值,像这样:
int result = triple(2);
下面你将知道如何声明并创建一个需要两个int型参数,将它们相乘然后返回结果的代码块:
int (^multiply)(int, int) = ^(int x, int y) {
return x * y;
};
这是如何调用这个代码块:
int result = multiply(2, 3);
声明代码块变量使我们有机会探讨代码块类型以及如何调用。代码块变量类似函数指针,调用代码块与调用函数相似。不同于函数指针的是,代码块实际上是Objective-C对象,这意味着我们可以像对象一样传递它们。
调用代码块的方法
在实际中,代码块经常被作为参数传入方法中供其回调。当把代码块作为一个参数时,相比分配一个代码块变量,更通常的做法是作为内联代码块。例如,我们之前看到的例子:view animations 和enumeration。
苹果官方已经增加了一些使用代码块的方法到他们的框架中。你也可以写一些使用代码块的API了。例如,我们要创建一个Worker类的使用代码块的类方法,该方法重复调用代码块指定的次数,并处理代码块每次返回的结果。下面是我们使用内联代码块调用这个方法,代码块负责返回1到10的每个数的三倍。
[Worker repeat:10 withBlock:^(int number) {
return number * 3;
}];
这个方法可以将任何接受一个int型参数并返回一个int型结果的代码块作为参数,如果想得到数字的二倍,只需要改变传入方法的代码块。
到你动手的时候了
那么你将如何去实现上面我们使用的 repeat:withBlock:这个方法,考虑一下,我们将在下期给出分解。在此期间,通过调用enumerateKeysAndObjectsUsingBlock: 方法打印一个NSDictiobary的键和值来练习使用代码块:
NSDictionary *cards =
[NSDictionary dictionaryWithObjectsAndKeys:@"Queen", @"card",
@"Hearts", @"suit",
@"10", @"value", nil];