讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 2.11 匿名函數 >

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}