一般形式:{ (parameters) -> returnType in statements }
這裡的參數(parameters),可以是in-out(輸入輸出參數),但不能設定默認值。如果是可變參數,必須放在最後一位,不然編譯器報錯。元組也可以作為參數或者返回值。
"in"關鍵字表示閉包的參數和返回值類型定義已經完成,閉包函數體即將開始。即由in引入函數
例子
//一般形式 let calAdd:(Int,Int)->(Int) = { (a:Int,b:Int) -> Int in return a + b } print(calAdd(100,150)) //Swift可以根據閉包上下文推斷參數和返回值的類型,所以上面的例子可以簡化如下 let calAdd2:(Int,Int)->(Int) = { a,b in //也可以寫成(a,b) in return a + b } print(calAdd2(150,100)) //上面省略了返回箭頭和參數及返回值類型,以及參數周圍的括號。當然你也可以加括號,為了好看點,看的清楚點。(a,b) //單行表達式閉包可以隱式返回,如下,省略return let calAdd3:(Int,Int)->(Int) = {(a,b) in a + b} print(calAdd3(50,200)) //如果閉包沒有參數,可以直接省略“in” let calAdd4:()->Int = {return 100 + 150} print("....\(calAdd4())") //這個寫法,我隨便寫的。打印出“我是250” //這個是既沒有參數也沒返回值,所以把return和in都省略了 let calAdd5:()->Void = {print("我是250")} calAdd5()
歸納
閉包類型是由參數類型和返回值類型決定,和函數是一樣的。比如上面前三種寫法的閉包的閉包類型就是(Int,Int)->(Int)
,後面的類型分別是()->Int
和()->Void
。分析下上面的代碼:let calAdd:(add類型)
。這裡的add類型就是閉包類型 (Int,Int)->(Int)
。意思就是聲明一個calAdd常量,其類型是個閉包類型。
"="右邊是一個代碼塊,即閉包的具體實現,相當於給左邊的add常量賦值。兄弟們,是不是感覺很熟悉了,有點像OC中的block代碼塊。
也可以關鍵字“typealias”先聲明一個閉包數據類型。類似於OC中的typedef起別名
typealias AddBlock = (Int, Int) -> (Int) let Add:AddBlock = { (c,d) in return c + d } let Result = Add(100,150) print("Result = \(Result)")
若將閉包作為函數最後一個參數,可以省略參數標簽,然後將閉包表達式寫在函數調用括號後面
func testFunction(testBlock: ()->Void){ //這裡需要傳進來的閉包類型是無參數和無返回值的 testBlock() } //正常寫法 testFunction(testBlock: { print("正常寫法") }) //尾隨閉包寫法 testFunction(){ print("尾隨閉包寫法") } //也可以把括號去掉,也是尾隨閉包寫法。推薦寫法 testFunction { print("去掉括號的尾隨閉包寫法") }
閉包可以在其被定義的上下文中捕獲常量或變量。Swift中,可以捕獲值的閉包的最簡單形式是嵌套函數,也就是定義在其他函數的函數體內的函數。
func captureValue(sums amount:Int) -> ()->Int{ var total = 0 func incrementer()->Int{ total += amount return total } return incrementer } print(captureValue(sums: 10)()) print(captureValue(sums: 10)()) print(captureValue(sums: 10)()) //打印"10 10 10"
這裡沒有值捕獲的原因是,沒有去用一個常量或變量去引用函數,所以每次使用的函數都是新的。有點類似於OC中的匿名對象。
let referenceFunc = captureValue(sums: 10) print(referenceFunc()) print(referenceFunc()) print(referenceFunc()) //打印"10 20 30"
這裡值捕獲了,是因為函數被引用了,所以沒有立即釋放掉。所以函數體內的值可以被捕獲
閉包形式
func captureValue2(sums amount:Int) -> ()->Int{ var total = 0 let AddBlock:()->Int = { total += amount return total } return AddBlock } let testBlock = captureValue2(sums: 100) print(testBlock()) print(testBlock()) print(testBlock())
由上面的例子都可以證得,函數和閉包都是引用類型。
當一個閉包作為參數傳到一個函數中,需要這個閉包在函數返回之後才被執行,我們就稱該閉包從函數種逃逸。一般如果閉包在函數體內涉及到異步操作,但函數卻是很快就會執行完畢並返回的,閉包必須要逃逸掉,以便異步操作的回調。
逃逸閉包一般用於異步函數的回調,比如網絡請求成功的回調和失敗的回調。語法:在函數的閉包行參前加關鍵字“@escaping”。
//例1 func doSomething(some: @escaping () -> Void){ //延時操作,注意這裡的單位是秒 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { //1秒後操作 some() } print("函數體") } doSomething { print("逃逸閉包") } //例2 var comletionHandle: ()->String = {"約嗎?"} func doSomething2(some: @escaping ()->String){ comletionHandle = some } doSomething2 { return "叔叔,我們不約" } print(comletionHandle()) //將一個閉包標記為@escaping意味著你必須在閉包中顯式的引用self。 //其實@escaping和self都是在提醒你,這是一個逃逸閉包, //別誤操作導致了循環引用!而非逃逸包可以隱式引用self。 //例子如下 var completionHandlers: [() -> Void] = [] //逃逸 func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) } //非逃逸 func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() } class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } someFunctionWithNonescapingClosure { x = 200 } } }
顧名思義,自動閉包是一種自動創建的閉包,封裝一堆表達式在自動閉包中,然後將自動閉包作為參數傳給函數。而自動閉包是不接受任何參數的,但可以返回自動閉包中表達式產生的值。
自動閉包讓你能夠延遲求值,直到調用這個閉包,閉包代碼塊才會被執行。說白了,就是語法簡潔了,有點懶加載的意思。
var array = ["I","have","a","apple"] print(array.count) //打印出"4" let removeBlock = {array.remove(at: 3)}//測試了下,這裡代碼超過一行,返回值失效。 print(array.count) //打印出"4" print("執行代碼塊移除\(removeBlock())") //打印出"執行代碼塊移除apple" 這裡自動閉包返回了apple值 print(array.count) //打印出"3"