Swift學習-2閉包
記錄學習Swift過程,原網址是:https://www.journaldev.com/15163/swift-closure
閉包:
函式也是閉包的一種;閉包是沒有名字的函式,也沒有識別符號func.
三種形式:全域性閉包、巢狀閉包、閉包表示式;
前兩種函式中已經討論過了;一般我們所提到的閉包都是第三者形式。
閉包優勢:
- 閉包比函式簡單,swift可以從定義閉包的上下文中推斷出引數型別和返回型別,從而便於定義和傳遞給函式。
- 閉包可以用於捕獲和儲存某個特定時間點的某個變數的狀態,並在以後使用它
- 閉包允許我們在函式返回後執行一段程式碼
定義閉包
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