swift 語言的基礎 - 閉包 (Closure)

  1. 閉包(Closure) - 介紹
    1. 基本型態
    2. 進階變化
      1. 範例1
      2. 經典範例2
    3. 尾隨閉包 Trailing Closure
    4. 捕獲值 Capture Value
    5. 非逃逸閉包 NoEscape Closure
    6. 自動閉包 Auto Closure

閉包(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]