閉包(Closure) - 介紹
這裡是學習 swift 的一個關卡,不要急,先理解,模仿,再求變化
Reference Type
基本型態
{ (parameters) -> returnType in
// statements
}
parameters: 由外部傳進來的參數
returnType: 要回傳的型別
in: 將宣告與程式區塊分離的介詞
來一個簡單的 hello world 範例
var sayHi = { print("hello world")}
sayHi()
加入參數
var sayHi = { (name:String) in print("hello world, \(name)!!")}
sayHi("Sweetie")
回傳值
var sum = { (number1 :Int, number2:Int) in
number1 + number2
}
print(sum(3,5)) //8
進階變化
把閉包當做是一個參數丟到函數裡
範例1
假如要做一個計算機,裡面需要把兩個數做處理, 寫法如下
let operatorAdd = {(n1:Int,n2:Int) in
return n1 + n2
}
func calculate(_ num1:Int,_ num2:Int,_ opertator:(Int,Int)->Int ) -> Int{
return opertator(num1,num2)
}
print(calculate(3,5,operatorAdd)) //8
如果要實作減法,那就寫一個減法的閉包放進去,就會兩數相減了,是不是很神奇???
let operatorMinus = {(n1:Int,n2:Int) in
return n1 - n2
}
print(calculate(3,5,operatorMinus)) //-1
經典範例2
var names = ["aki","bill","cat"]
var compare = { (s1: String, s2: String) -> Bool in
return s1 < s2
}
print(names.sorted(by: compare)) //["cat","bill","aki"],
//其中又可以省略成
names.sorted(by:(s1,s2) in s1<s2)
//超省略
names.sorted(by:{$0<$1})
//終極省略
names.sorted(by: <) //Operator Methods
//所以~~~ 範例 1 的函數,可以省略成
calculate(3,8,+) //11
calculate(3,8,-) //-5
//傑克,真是太神奇了
尾隨閉包 Trailing Closure
當閉包做為函數的參數,又剛好是最後一個的時候,就稱之為尾隨閉包,為什麼要介紹這個呢?因為 swift 很好心的想要簡略一些寫法。
先來一個變化前的函數
香腸產生器,將調味函數做成一個閉包參數,結果如下
var seasoning = {
print("加鹽巴")
}
func sausageGenerator(meat:String,closureSeasoning: () -> ()) {
print(meat)
closureSeasoning()
})
//呼叫
sausageGenerator("肉肉",seasoning)
//有些閉包的寫法 可以省略 ()->() 直接寫成 {}
sausageGenerator("肉肉",closureSeasoning:{print("加醬油")})
因為閉包是最後一個參數,在呼叫函數時也可以改寫為
sausageGenerator(meat:meat) {
//原本放在 closureSeasoning 閉包裡的內容 就可以寫在這裡了
print("加胡椒")
})
捕獲值 Capture Value
在閉包裡的程式區塊之中,在記憶體還沒回收之前,儲存的值是一直存在的,我們稱之為捕獲,舉例如下:
//函數會產生一個加鹽巴的閉包
func addSalt() -> () -> Void {
var saltCount = 1
return {
print("加了 \(saltCount) 次鹽巴")
saltCount += 1
}
}
//做一個閉包來使用
var addSaltClosure = addSalt()
sausageGenerator(meat:"肉",closureSeasoning:addSaltClosure)
sausageGenerator(meat:"肉",closureSeasoning:addSaltClosure)
sausageGenerator(meat:"肉",closureSeasoning:addSaltClosure)
sausageGenerator(meat:"肉",closureSeasoning:addSaltClosure)
//
//肉
//加了 1 次鹽巴
//肉
//加了 2 次鹽巴
//肉
//加了 3 次鹽巴
//肉
//加了 4 次鹽巴
發現了一個有趣的結果,saltCount 的值是一直被捕獲在閉包的程式區塊之中,隨著程式執行四次,就被遞增了四次。意即變數會一直被抓住,直到這個閉包被記憶體釋放後,saltCount 才會被消失。
那麼, 如果我們把程式外部的值傳到閉包裡,外部的值如果在閉包裡被捕獲,又要在外部修改,在這個當下就會產生程式的衝突,一個要拿,一個卻緊緊不放。
為了解決這個問題,請看另一個章節 swift 語言的特性 (Strong, Weak, UnOwned)
非逃逸閉包 NoEscape Closure
當我們把閉包當做是一個參數丟到函數裡的時候,而在函數執行返回之後,才會繼續執行閉包,如果很明確的只要閉包乖乖當一個參數,生命週期只在函數裡完成,不再使用 reference type 的特性,將裡面的值接著向外操作,那麼只要加上個 @noescape 的前綴,就可以限制閉包的逃逸
var seasoning = {
print("加鹽巴")
}
func sausageGenerator(meat:String, @noescape closureSeasoning: () -> ()) {
print(meat)
closureSeasoning()
})
自動閉包 Auto Closure
範例如下:
func sausageGenerator(meat:String,season: @autoclosure () -> ()) {
greet()
}
// pass closure without {}
sausageGenerator(meat: "肉", season: print("加鹽巴"))
轉載請註明來源,歡迎對文章中的引用來源進行考證,歡迎指出任何有錯誤或不夠清晰的表達。可以郵件至 [email protected]