1. 程式人生 > 其它 >Swift學習-2閉包

Swift學習-2閉包

記錄學習Swift過程,原網址是:https://www.journaldev.com/15163/swift-closure

閉包:

函式也是閉包的一種;閉包是沒有名字的函式,也沒有識別符號func.
三種形式:全域性閉包、巢狀閉包、閉包表示式;
前兩種函式中已經討論過了;一般我們所提到的閉包都是第三者形式。

閉包優勢:

  1. 閉包比函式簡單,swift可以從定義閉包的上下文中推斷出引數型別和返回型別,從而便於定義和傳遞給函式。
  2. 閉包可以用於捕獲和儲存某個特定時間點的某個變數的狀態,並在以後使用它
  3. 閉包允許我們在函式返回後執行一段程式碼

定義閉包

1.基本語法:

{}:代表閉包主體
(parameters) : 入參及引數型別
-> (return type) : 返回與返回值型別
in : 將引數和返回型別與主體分開。
{(parameters) -> (return type) in

}

2.定義一個閉包變數

var myClosure : (ParameterTypes) -> ReturnType

示例

1.定義一個函式和一個閉包,無參無返回

列印Hello world

//函式
func helloWorldFunc(){
    print("Hello world")
}
//閉包
var helloWorldClosure = {() -> () in
    print("Hello world")
}

當我們不需要有返回值,或者返回值型別可以從上下文推斷出來,我們可以省略返回部分的程式碼;
當我們沒有入參,或者入參型別可以從上下文推斷出來時,我們也可以省略引數部分的程式碼
所以上面的閉包可以簡化為:

var helloWorldClosureSimply = {
    print("Hello world")
}

2.定義一個有參有返回的函式和閉包

將兩個字串拼接起來

func appendString(_ a:String,with b:String) -> String{
    return a + " : " + b
}
print(appendString("Swift", with: "Functions")) //列印結果: "Swift : Functions"

var appendStringClosure = {(a: String,b : String) -> String in
    return a + " : " + b
}
print(appendStringClosure("Swift","Closure"))//列印結果:Swift : Closure

//由於swift可以從上下文中推斷出入參與返回值型別,上面的閉包可以變換成以下幾種格式
//從入參型別,推斷出返回值型別,所以可以省略返回值型別
var appendStringClosureWithOutReturnType = {(a:String,b:String) in
    return a + " : " + b
}

var appendStringClosureTypeDeclaredOnTheVariable : (String,String) -> String = {(a,b) in
    return a + " : " + b  //也可以省略return關鍵字
}
//Swift允許我們使用簡寫的引數名稱來引用傳遞給closure的引數:分別為第一個、第二個、第三個引數引用$0、$1、$2等引數。
var appendStringUseShortHandName : (String,String) -> String = {
    $0 + " : " + $1
}

var appendStringUseShortHandNameTakeOneOrMoreAhead = {
    $0 + " : " + $1 + " : " + $2
}

print(appendStringUseShortHandNameTakeOneOrMoreAhead("Swift", "Closures", "Awesome")) //列印結果: "Swift : Closures Awesome"

3.建立一個用函式或閉包做引數的函式

func operationsSq(a:Int,b:Int,myFunction:(Int,Int) -> Int) -> Int{
    return myFunction(a,b)
}
//建立函式作為引數也是可以實現,但是會增加樣板程式碼
func addSquareFunc(_ a:Int,_ b:Int) -> Int{
    return a*a + b*b
}
operationsSq(a:2,b:4,myFunction:addSquareFunc)//列印結果:20

//下面使用閉包
var addSquareClosure : (Int,Int) -> Int = {$0 * $0 + $1 * $1}
var subtractSquareClosure: (Int, Int) -> Int = { $0*$0 - $1*$1 }
var multiplySquareClosure: (Int, Int) -> Int = { $0*$0 * $1*$1 }
var divideSquareClosure: (Int, Int) -> Int = { ($0*$0) / ($1*$1) }
var remainderSquareClosure: (Int, Int) -> Int = { ($0*$0) % ($1*$1) }
operationsSq(a:4,b:2,myFunction:addSquareClosure)//列印結果:20
operationsSq(a:4,b:2,myFunction:subtractSquareClosure)//列印結果:12
operationsSq(a:4,b:2,myFunction:multiplySquareClosure)//列印結果:64
operationsSq(a:4,b:2,myFunction:divideSquareClosure)//列印結果:4
operationsSq(a:4,b:2,myFunction:remainderSquareClosure)//列印結果:0

因為Swift中表明,如果閉包中的入參和返回值可以從上下文中判斷出來時,可以省略入參和返回,所以將上面定義的閉包變數進行了如下嘗試:

var add = {$0 * $0 + $1 * $1}

但是一直報錯 錯誤原因為:Ambiguous use of operator '*';瞭解了一下,因為沒有確認入參型別和出參型別,結合上下文也不能確認型別。所以改為下面的程式碼,就不報錯了,所以不確認輸入或輸出型別時,body體中一定要給出一個有型別的資料。

var add = {$0 * $0 *  + $1 * $1 * 1}

言歸正傳。然後也可以不定義閉包變數,直接寫在呼叫函式中

operationsSq(a:2,b:2, myFunction: { $0*$0 + $1*$1 })
operationsSq(a:4,b:5, myFunction: { $0*$0 - $1*$1 })
operationsSq(a:5,b:5, myFunction: { $0*$0 * $1*$1 })
operationsSq(a:10,b:5, myFunction: { ($0*$0) / ($1*$1) })
operationsSq(a:7,b:5, myFunction: { ($0*$0) % ($1*$1) })

如果閉包在函式末尾,則函式也可以簡寫如下:

operationsSq(a:2,b:2){
    ($0*$0) + ($1*$1)
}

下面看一個例子,我們將用閉包表示式將指數相加

func sumOfExponentials(from:Int,to:Int,myClosure:(Int)->Int) -> Int{
    var sum = 0
    for i in from...to{
        sum = sum + myClosure(i)
    }
    print(sum)
    return sum
}

sumOfExponentials(from:0,to:5){$0} //0-5相加之和
sumOfExponentials(from:0,to:5){$0 * $0}//0-5平方之和
sumOfExponentials(from:0,to:5){$0 * $0 * $0}//0-5立方之和

4.map、sorted也是一種尾隨閉包

//將整數型陣列轉變為字元型陣列
var numbersArray = [1,2,3,4,5,6]
var closureString = numbersArray.map{
    return "\($0)"
}
print(closureString)//列印結果:["1", "2", "3", "4", "5", "6"]

//使用尾隨閉包按降序排列陣列
var descendingArray = numbersArray.sorted{$0 > $1}
print(descendingArray)//列印結果:[6, 5, 4, 3, 2, 1]

5.捕獲閉包中的列表

var y = 0
var myClosure = {
    print("The value of x is start of \(y)")
}
myClosure()//列印結果:The value of x is start of 0

//但是當我們重新為 y 賦值時,
y = 5
myClosure()//列印結果:The value of x is start of 5

myClosure定義和初始化都在y = 5 賦值之前,為什麼結果是The value of x is start of 5?因為閉包捕獲的是y的引用(記憶體地址)。對該記憶體地址的值所做的任何更改都將在呼叫它時由閉包顯示。要使x表現為值型別,我們需要使用Capture Lists。Capture Lists是一個數組[],它儲存變數的本地副本。

//修改如下:
var variable = 5
var capturesClosure = { [variable] in
    print("The value of variable is start of \(variable)")
}
capturesClosure()
variable = 10
capturesClosure()

此時再改變變數的值,閉包中的值也不會改變;

5.防止強引用造成記憶體洩露示例

捕獲引用型別在類中使用時可能是破壞性的,因為閉包可能持有對例項變數的強引用,並導致記憶體洩漏。我們來看一個例子

class Person {

    var x: Int
    var myClosure: ()->() = {print("Hey there")}
    init(x: Int)
    {
    self.x = x
    }
    func initClosure()
    {
        myClosure = { print("Initial value is not defined yet")}
    }

    deinit{
    print("\(self) escaped")
    }


}

var a:Person? = Person(x: 0)
a?.initClosure()
a?.x = 5
a?.myClosure()
a = nil

列印結果:Initial value is not defined yet __lldb_expr_12.Person escaped
類正常釋放

class Person {

   var x: Int
    var myClosure: ()->() = {print("Hey there")}
    init(x: Int)
    {
    self.x = x
    }

    func initClosure()
    {
        myClosure = { print("Intial value is \(self.x)")}
    }

   deinit{
    print("\(self) escaped")
    }

}

var a:Person? = Person(x: 0)
a?.initClosure()
a?.x = 5
a?.myClosure()//列印結果:Intial value is 5
a = nil

deinit中未列印,因為閉包強引用了self.x,修改如下:

class Person {

    var x: Int
    var myClosure: ()->() = {print("Hey there")}
    init(x: Int)
    {
    self.x = x
    }

    func initClosure()
    {
        myClosure = {[weak self] in guard let weakSelf = self else { return }
            print("Intial value is \(weakSelf.x)")}
    }

    deinit{
    print("\(self) escaped")
    }


}

var a:Person? = Person(x: 0)
a?.initClosure()
a?.x = 5//列印結果:Intial value is 5
a?.myClosure()
a = nil//列印結果:__lldb_expr_16.Person escaped

因為弱引用是這樣一種引用,它不會對它所引用的例項保持強持有,因此不會阻止ARC處理被引用的例項.

6.逃逸(轉義)閉包

1.轉義閉包是在傳遞給它的函式返回後呼叫的閉包。換句話說,它比傳遞給它的函式更長壽。轉義閉包通常用於完成處理程式,因為它們在函式結束後被呼叫。
2.非轉義閉包是在傳遞給它的函式中呼叫的閉包,即在它返回之前。預設情況下,閉包是不可轉義的

var completionHandlers: [() -> Void] = []

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)

}
class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure {[weak self] in guard let weakSelf = self else { return }
            weakSelf.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
    
    deinit{
    print("deinitalised")
    }
}

var s:SomeClass? = SomeClass()
s?.doSomething()
print(s?.x ?? -1) //prints 200


completionHandlers.first?()
print(s?.x ?? -1) //prints 100
s = nil