再来看看之前的示例:
func whatToAnimate { // self.myButton is a button in the interface self.myButton.frame.origin.y += 20}func whatToDoLater(finished:Bool) { print(/"finished: (finished)/")}UIView.animateWithDuration( 0.4, animations: whatToAnimate, completion: whatToDoLater)
上述代码有些丑陋。我声明函数whatToAnimate与whatToDoLater的唯一目的就是将它们传递给最下面一行的函数。我其实并不需要whatToAnimate与whatToDoLater这两个名字,只不过就是为了在最后一行代码中引用它们而已;无论是名字还是这两个函数后面都不会再用到了。因此,要是不需要名字而只传递这两个函数的函数体就好了。
这叫作匿名函数,在Swift中是合法且常见的。为了构造匿名函数,你需要完成两件事:
1.创建函数体本身,包括外面的花括号,但不需要函数声明。
2.如果必要,将函数的参数列表与返回类型作为花括号中的第1行,后跟关键字in。
下面将之前的具名函数声明转换为匿名函数。如下是whatToAnimate的具名函数声明:
func whatToAnimate { self.myButton.frame.origin.y += 20}
下面是完成相同事情的匿名函数。注意我是如何将参数列表与返回类型移到花括号中的:
{ -> in self.myButton.frame.origin.y += 20}
下面是whatToDoLater的具名函数声明:
func whatToDoLater(finished:Bool) { print(/"finished: (finished)/")}
下面是完成相同事情的匿名函数:
{ (finished:Bool) -> in print(/"finished: (finished)/")}
现在我们既然已经知道了如何创建匿名函数,下面就来使用它们。在向animateWith-Duration传递参数时需要函数。我们可以在这个地方创建并传递匿名函数,如以下代码所示:
UIView.animateWithDuration(0.4, animations: { -> in self.myButton.frame.origin.y += 20 }, completion: { (finished:Bool) -> in print(/"finished: (finished)/")})
我们可以像2.10节调用imageOfSize函数那样做出相同的改进。之前是这样调用函数的:
func drawing { let p = UIBezierPath( roundedRect: CGRectMake(0,0,45,20), cornerRadius: 8) p.stroke}let image = imageOfSize(CGSizeMake(45,20), drawing)
不过,现在知道并不需要单独声明drawing函数。我们可以通过匿名函数来调用image-OfSize:
let image = imageOfSize(CGSizeMake(45,20), { let p = UIBezierPath( roundedRect: CGRectMake(0,0,45,20), cornerRadius: 8) p.stroke})
匿名函数在Swift中使用非常普遍,因此请确保你能够读懂并编写这样的代码!事实上,匿名函数非常常见且非常重要,因此Swift提供了以下一些便捷写法。
省略返回类型
如果编译器知道匿名函数的返回类型,那么你就可以省略箭头运算符及返回类型说明:
UIView.animateWithDuration(0.4, animations: { in self.myButton.frame.origin.y += 20 }, completion: { (finished:Bool) in print(/"finished: (finished)/")})
如果没有参数,那么可以省略in这一行
如果匿名函数不接收参数,并且返回类型可以省略,那么in这一行就可以被完全省略:
UIView.animateWithDuration(0.4, animations: { self.myButton.frame.origin.y += 20 }, completion: { (finished:Bool) in print(/"finished: (finished)/")})
省略参数类型
如果匿名函数接收参数,并且编译器知道其类型,那么类型就是可以省略的:
UIView.animateWithDuration(0.4, animations: { self.myButton.frame.origin.y += 20 }, completion: { (finished) in print(/"finished: (finished)/")})
省略圆括号
如果省略参数类型,那么包围参数列表的圆括号也可以省略:
UIView.animateWithDuration(0.4, animations: { self.myButton.frame.origin.y += 20 }, completion: { finished in print(/"finished: (finished)/")})
有参数时也可以省略in这一行
如果返回类型可以省略,并且编译器知道参数类型,那就可以省略in这一行,直接在匿名函数体中引用参数,方式是使用魔法名$0、$1等,并且要按照顺序引用:
UIView.animateWithDuration(0.4, animations: { self.myButton.frame.origin.y += 20 }, completion: { print(/"finished: ($0)/")})
省略参数名
如果匿名函数体不需要引用某个参数,那就可以在in这一行通过下划线来代替参数列表中该参数的名字;事实上,如果匿名函数体不需要引用任何参数,那就可以通过一个下划线来代替整个参数列表:
UIView.animateWithDuration(0.4, animations: { self.myButton.frame.origin.y += 20 }, completion: { _ in print(/"finished!/")})
不过请注意,如果匿名函数接收参数,那就必须要以某种方式承认它们的存在。可以省略in这一行,然后通过魔法名$0等来使用参数,或是保留in这一行,然后通过下划线省略参数,但不能在省略in这一行的同时又不通过魔法名来使用参数!如果这么做了,那么代码将无法编译通过。
省略函数实参标签
如果匿名函数是函数调用的最后一个参数,那么你可以在最后一个参数前通过右圆括号关闭函数调用,然后放置匿名函数体且不带任何标签(这叫作尾函数):
UIView.animateWithDuration(0.4, animations: { self.myButton.frame.origin.y += 20 }) { _ in print(/"finished!/")}
省略调用函数圆括号
如果使用尾函数语法,并且调用的函数只接收传递给它的函数,那就可以在调用中省略空的圆括号。这是唯一一个可以从函数调用中省略圆括号的情形。下面声明并调用一个不同的函数:
func doThis(f:->) { f}doThis { // no parentheses! print(/"Howdy/")}
省略关键字return
如果匿名函数体只包含一条语句,并且该语句使用关键字return返回一个值,那么关键字return就可以省略。换句话说,在函数会返回一个值的上下文中,如果匿名函数体只包含了一条语句,那么Swift就会假设该语句是个表达式,其值会从匿名函数中返回:
func sayHowdy -> String { return /"Howdy/"}func performAndPrint(f:->String) { let s = f print(s)}performAndPrint { sayHowdy // meaning: return sayHowdy}
在编写匿名函数时,你可以充分利用上面介绍的各种省略形式。此外,你还可以将整个匿名函数作为一行放到函数调用中,从而减少代码占据的行数(但不会减少代码量)。这样,涉及匿名函数的Swift代码就会变得非常紧凑了。
下面是个典型的示例。首先定义了一个Int值的数组,然后生成一个新数组,新数组中的每个值都是原数组值乘以2,方式是调用map实例方法。数组的map方法接收一个函数,该函数接收一个参数,并返回一个与数组元素相同类型的值;这里的数组由Int值构成,因此需要向map方法传递一个接收一个Int值并返回一个Int值的函数。整个函数的代码如下所示:
let arr = [2, 4, 6, 8]func doubleMe(i:Int) -> Int { return i*2}let arr2 = arr.map(doubleMe) // [4, 8, 12, 16]
不过,这么写不太符合Swift的风格。其他地方并不需要doubleMe这个名字,因此它可以作为一个匿名函数。其返回类型是已知的,因此无须指定;其参数类型是已知的,因此也无须指定。我们只需要使用一个参数,因此并不需要in这一行,只要用$0来引用该参数即可。函数体只包含了一条语句,它是个return语句,因此可以省略return。map不再接收其他参数,因此可以省略圆括号,在名字后直接跟着尾函数即可:
let arr = [2, 4, 6, 8]let arr2 = arr.map {$0*2}