(Swift) Closure 學習筆記
Var 變數也能存取 Function,這時候變數的型別就會是 Function 型別
一般的 Function (沒有回傳值)
func buyIphone(model: String, size: String) {
print("你買了\(model) \(size) 寸")
}
buyIphone(model: "iphone14 pro", size: "5.8")
// 你買了iphone14 pro 5.8 寸
相等的寫法
func buyIphone(model: String, size: String) -> () {
print("你買了\(model) \(size) 寸")
}
func buyIphone(model: String, size: String) -> Void {
print("你買了\(model) \(size) 寸")
}
將變數存取 buyIphone,我們就可以發現 buy1 的型別就會是 Function 型別
let buy1: (String, String) -> () = buyIphone
buy1("iphone14 pro", "5.8")
// 你買了iphone14 pro 5.8 寸
那如果將 buyIphone 加上括號呢? buyIphone()
let buy1: (String, String) -> () = buyIphone()
這時候會跳出錯誤,當我們寫出 buyIphone() 時,實際上是在呼叫 buyIphone 這個 Function,且我們也少了兩個參數。
那改成下方寫法應該就不會出錯了吧?
let buy1: (String, String) -> () = buyIphone(model: "iphone14 pro", size: "5.8")
那為什麼還是會出錯呢?答案其實就是剛剛有提到的,因為寫 buyIphone() 是呼叫函數,但是我們的函數是沒有回傳值的,所以類型是不匹配的。
如何寫才不會出錯
let buy1 = buyIphone(model: "iphone14 pro", size: "5.8")
print(buy1)
// ()
這時會發現
print(buy1)
得到 (),原因就在於 buyIphone() 是沒有回傳值的
接下來相等的寫法 buy2
let buy1: (String, String) -> () = buyIphone
buy1("iphone14 pro", "5.8")
// 你買了iphone14 pro 5.8 寸
let buy2 = buyIphone
buy2("iphone14 pro", "5.8")
// 你買了iphone14 pro 5.8 寸
有回傳值的 Function
func buyIphone(model: String, size: String) -> String {
return "你買了\(model) \(size) 寸"
}
let buy1 = buyIphone // (String, String) -> String
print(buy1)
// (Function)
let buy2 = buyIphone(model: "iphone14 pro", size: "5.8") // 你買了iphone14 pro 5.8 寸
print(buy2)
// 你買了iphone14 pro 5.8 寸
() 就是 呼叫 Function,也就是上面不斷提到的,所以它會有回傳值賦予給 buy2
化身參數的 Function
// model 型別為 String
func buyIphone(model: String) -> String {
return "你買了\(model) "
}
// model 型別為 () -> String
func buyIphone(model: () -> String) -> String {
return "你買了\(model()) "
}
使用 buyIphone 函數
func buyIphone(model: () -> String) -> String {
return "你買了\(model()) "
}
定義一個無參數,且回傳值為 String 的 Function 傳入 buyIphone
func iphone14() -> String {
return "iphone14"
}
buyIphone(model: iphone14)
接受參數的 function 型別
func buyIphone(model: (Int) -> String) -> String {
return "你買了\(model(3)) "
}
func iphone14(count: Int) -> String {
return "\(count) 隻 iphone14"
}
buyIphone(model: iphone14)
但程式這樣設計會有一點麻煩,若是要購買其他商品,就必須定義多個 Function
func iphone14() -> String {
return "iphone14"
}
func iphone15() -> String {
return "iphone15"
}
buyIphone(model: iphone14)
buyIphone(model: iphone15)
Closure
我們可以使用 Closure 解決上述的問題,那到底該怎麼做呢?將 buyIphone 中的 model 回傳值,改成下方的 closure 表達式。
buyIphone(model: { () -> String in
return "iphone14"
})
這時候你就不需要定義 Function,就能傳入任何的產品
buyIphone(model: { () -> String in
return "iphone14"
})
buyIphone(model: { () -> String in
return "iphone14 Pro"
})
buyIphone(model: { () -> String in
return "iphone14 Pro Max"
})
更簡潔的寫法,省略回傳的 型別 與 in
buyIphone(model: {
return "iphone14"
})
在 Swift 中若是回傳的程式碼只有一行時,可以省略 return
buyIphone(model: {
"iphone14"
})
最終還能進一步簡化程式
buyIphone {
"iphone14"
}
但是這是有一些限制的,可以看下方的 trailing closure 說明。
Trailing closure
若是 Function 中的參數,在最右邊為 Function 型別時,能夠有更佳簡潔的寫法,按下 Enter 試試看!
func buyIphone(count: Int, model: () -> String) -> String {
return "你買了\(model()) "
}
buyIphone(count: 3) {
"iphone14"
}
這時會發現參數 model 會消失,以及 ()也移動了。
來看看 Function 型別不為最右邊時的情況
func buyIphone(model: () -> String, count: Int) -> String {
return "你買了 \(count) 隻 \(model()) "
}
buyIphone(model: {
"iphone14"
}, count: 3)
這時的 buyIphone 就會變得有點不直觀了,所以記得當如果只有一個參數為 Function 型別時,記得放在最右邊。
接受參數的 Closure
func buyIphone(count: Int, model: (Int, String) -> Void) {
for i in 1...count {
model(i, "Iphone")
}
}
Closure 的寫法
buyIphone(count: 3, model: { (count: Int, model: String) -> Void in
print("今年買了 \(count) 次 \(model)")
})
簡化後面的 Void
buyIphone(count: 3, model: { (count: Int, model: String) in
print("今年買了 \(count) 次 \(model)")
})
簡化前面的參數
buyIphone(count: 3, model: { count, model in
print("今年買了 \(count) 次 \(model)")
})
簡化 model
buyIphone(count: 3) { count, model in
print("今年買了 \(count) 次 \(model)")
}
進一步簡化 Closure 參數
buyIphone(count: 3) {
print("今年買了 \($0) 次 \($1)")
}
$0 代表第一個參數,$1 就代表第二個參數
使用 forEach 來加深印象
let names = ["Jason", "Peter", "Kevin"]
names.forEach { name in
print(name)
}
names.forEach {
print($0)
}
使用 Closure 的副作用
先嘗試輸入以下程式碼
class Order {
var name = "Jason"
var model: (() -> Void)!
func buyIphone(buyAction: () -> Void) {
buyAction()
model = buyAction
}
}
Error message
會出現這個錯誤的原因,就在於 Function buyIphone() 中的 buyAction 型別為 function 型別,function 型別的參數是無法在 buyIphone Function 外做使用的,若要做使用的話,就必須加上 @escaping
,這樣一來你的參數就能在 buyIphone() 結束後繼續做使用。
escaping : 逃脫。可讓 closure 從 function 逃脫,function 執行後仍可繼續使用。
// swift 修正後的
class Order {
var name = "Jason"
var model: (() -> Void)!
func buyIphone(buyAction: @escaping () -> Void) {
buyAction()
model = buyAction
}
}
var order = Order()
order.buyIphone {
print("IphoneXR")
}
order.model()
@escaping 的副作用:
存取自己的屬性或方法時須加 self
class Order {
var name = "Jason"
var model: (() -> Void)!
func buyIphone(buyAction: @escaping () -> Void) {
buyAction()
model = buyAction
}
}
class Customer {
var name = "Jason"
var order = Order()
func myOrder() {
order.buyIphone {
print("\(name) buy IphoneXR")
}
}
}
Error message
有兩種修正的方法:
第一個:
直接在 name 前面加上 self
func myOrder() {
order.buyIphone {
print("\(self.name) buy IphoneXR")
}
}
第二個:
在 { 旁加上 [self] in
,這樣在這使用到的屬性都無需加上 self.
func myOrder() {
order.buyIphone { [self] in
print("\(name) buy IphoneXR")
}
}
⭐️還有一種情況需要注意
當你將 參數的型別設為 Function 型別 optional 時,Swift 將認定此參數是 @escaping
這樣一來你就必須加上 self 或是 [self] in
class Order {
var name = "Jason"
func buyIphone(buyAction: (() -> Void)?) {
if let buyAction = buyAction {
buyAction()
}
}
}
class Customer {
var name = "Jason"
var order = Order()
func myOrder() {
order.buyIphone {
print("\(self.name) buy IphoneXR")
}
}
}
@autoclosure
先來看一下原本的程式碼
func buyIphone(model: () -> String) -> String {
return "你買了\(model()) "
}
buyIphone {
"IphoneXS"
}
在 () -> String
前面加上 @autoclosure
func buyIphone(model: @autoclosure () -> String) -> String {
return "你買了\(model()) "
}
buyIphone(model: "IphoneX")
現在要傳入的就會變成 String,它就能自動變成型別 () -> String 的 closure。
使用 @autoclosure 的好處,傳入的參數不一定會執行,或是傳入的參數延後執行
Ex:
func processResult(_ result: @autoclosure () -> Int) {
if result() > 0 {
print("Positive result")
} else {
print("Non-positive result")
}
}
processResult(5) // 輸出: Positive result
processResult(-3) // 輸出: Non-positive result
@autoclosure 還能夠與 @escaping 結合(在前面加上),且搭配的 function 型別必須沒有參數,因此 () -> Int可以,但 (Int) -> Int 不行。