首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》2.11 匿名函数

关灯直达底部

再来看看之前的示例:


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}