再來看看之前的示例:
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}