閉包是可以在你的代碼中被傳遞和引用的功能性獨(dú)立代碼塊。
閉包能夠捕獲和存儲(chǔ)定義在其上下文中的任何常量和變量的引用,這也就是所謂的閉合并包裹那些常量和變量,因此被稱為“閉包”,Swift 能夠?yàn)槟闾幚硭嘘P(guān)于捕獲的內(nèi)存管理的操作。
閉包表達(dá)式
閉包表達(dá)式語(yǔ)法有如下的一般形式:
{ (parameters) -> (return type) in
statements
}
閉包表達(dá)式語(yǔ)法能夠使用常量形式參數(shù)、變量形式參數(shù)和輸入輸出形式參數(shù),但不能提供默認(rèn)值??勺冃问絽?shù)也能使用,但需要在形式參數(shù)列表的最后面使用。元組也可被用來作為形式參數(shù)和返回類型。
在swift中,可以使用func定義一個(gè)函數(shù),也可以通過閉包表達(dá)式定義一個(gè)函數(shù)
//函數(shù)
func sum(_ v1:Int,_ v2:Int) -> Int {
v1 + v2
}
//閉包表達(dá)式
var fn = {
(v1:Int,v2:Int) in
return v1 + v2
}
print(sum(1, 2)) //3
print(fn(1,2)) //3
閉包表達(dá)式的簡(jiǎn)寫
第一種寫法
Swift 的標(biāo)準(zhǔn)庫(kù)提供了一個(gè)叫做sorted(by:)的方法,會(huì)根據(jù)你提供的排序閉包將已知類型的數(shù)組的值進(jìn)行排序。一旦它排序完成, sorted(by:) 方法會(huì)返回與原數(shù)組類型大小完全相同的一個(gè)新數(shù)組,該數(shù)組的元素是已排序好的。原始數(shù)組不會(huì)被 sorted(by:)方法修改。
let names = ["Chris","Alex","Ewa","Barry","Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
print(reversedNames) //["Ewa", "Daniella", "Chris", "Barry", "Alex"]
我們這里來使用一個(gè)閉包表達(dá)式
reversedNames = names.sorted(by: {(s1:String,s2:String) -> Bool in
return s1 > s2
})
第二種寫法:從語(yǔ)境中推斷類型
由于排序閉包為實(shí)際參數(shù)來傳遞給方法,Swift 就能推斷它的形式參數(shù)類型和返回類型。 sorted(by:)方法是在字符串?dāng)?shù)組上調(diào)用的,所以它的形式參數(shù)必須是一個(gè)(String, String) -> Bool 類型的函數(shù)。這意味著 (String, String)和 Bool類型不需要寫成閉包表達(dá)式定義中的一部分。因?yàn)樗械念愋投寄鼙煌茢?,返回箭頭( ->)和圍繞在形式參數(shù)名周圍的括號(hào)也能被省略
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
第三種寫法:從單表達(dá)式閉包隱式返回
單表達(dá)式閉包能夠通過從它們的聲明中刪掉return關(guān)鍵字來隱式返回它們單個(gè)表達(dá)式的結(jié)果,前面的栗子可以寫作:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
第四種寫法:簡(jiǎn)寫的實(shí)際參數(shù)名
Swift 自動(dòng)對(duì)行內(nèi)閉包提供簡(jiǎn)寫實(shí)際參數(shù)名,你也可以通過 1 , $2 等名字來引用閉包的實(shí)際參數(shù)值。
如果你在閉包表達(dá)式中使用這些簡(jiǎn)寫實(shí)際參數(shù)名,那么你可以在閉包的實(shí)際參數(shù)列表中忽略對(duì)其的定義,并且簡(jiǎn)寫實(shí)際參數(shù)名的數(shù)字和類型將會(huì)從期望的函數(shù)類型中推斷出來。 in 關(guān)鍵字也能被省略,因?yàn)殚]包表達(dá)式完全由它的函數(shù)體組成:
reversedNames = names.sorted(by: { $0 > $1 } )
這里, 1 分別是閉包的第一個(gè)和第二個(gè) String 實(shí)際參數(shù)。
第五種:運(yùn)算符函數(shù)
實(shí)際上還有一種更簡(jiǎn)短的方式來撰寫上述閉包表達(dá)式。Swift 的 String 類型定義了關(guān)于大于號(hào)( >)的特定字符串實(shí)現(xiàn),讓其作為一個(gè)有兩個(gè) String 類型形式參數(shù)的函數(shù)并返回一個(gè) Bool 類型的值。這正好與 sorted(by:) 方法的第二個(gè)形式參數(shù)需要的函數(shù)相匹配。因此,你能簡(jiǎn)單地傳遞一個(gè)大于號(hào),并且 Swift 將推斷你想使用大于號(hào)特殊字符串函數(shù)實(shí)現(xiàn):
reversedNames = names.sorted(by: >)
尾隨閉包
- 如果你需要將一個(gè)很長(zhǎng)的閉包表達(dá)式作為函數(shù)最后一個(gè)實(shí)際參數(shù)傳遞給函數(shù),使用尾隨閉包將增強(qiáng)函數(shù)的可讀性。
- 尾隨閉包是一個(gè)被書寫在函數(shù)形式參數(shù)的括號(hào)外面(后面)的閉包表達(dá)式
func exec(v1:Int,v2:Int,fn:(Int,Int) -> Int) {
print(fn(v1,v2))
}
//調(diào)用
exec(v1: 10, v2: 20){
$0 + $1
}
如果閉包表達(dá)式作為函數(shù)的唯一實(shí)際參數(shù)傳入,而你又使用了尾隨閉包的語(yǔ)法,那你就不需要在函數(shù)名后邊寫圓括號(hào)了
func exec(fn:(Int,Int) -> Int) {
print(fn(1,2))
}
exec(fn: {$0 + $1})
exec(){$0 + $1}
exec{$0 + $1}
閉包
一個(gè)函數(shù)和它所捕獲的變量或者常量環(huán)境組合起來,稱為閉包
- 一般指定義在函數(shù)內(nèi)部的函數(shù)
- 一般它捕獲的是外層函數(shù)的局部變量或者常量
typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 0
func plus (_ i:Int) -> Int{
num += i
return num
}
return plus
}
var fn = getFn()
fn(1)
fn(2)
fn(3)
fn(4)
對(duì)于這樣的函數(shù),打印結(jié)果值是什么呢?我們嘗試打印一下1 3 6 10,有沒有超出我們的預(yù)料。
捕獲值
一個(gè)閉包能夠從上下文捕獲已被定義的常量和變量。即使定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍能夠在其函數(shù)體內(nèi)引用和修改這些值。
想要了解上面的本質(zhì),我們可以簡(jiǎn)單的看一下匯編代碼
typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 0
func plus (_ i:Int) -> Int{
// num += i
return 0
}
return plus
}
var fn = getFn()
print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))
在return plus打一個(gè)斷點(diǎn),想要看匯編代碼工具欄 --> Debug --> Debug Workflow --> Always Show Disassembly

typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 0
func plus (_ i:Int) -> Int{
num += i
return num
}
return plus
}
var fn = getFn()
print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))
然后我們?cè)诳匆幌逻@個(gè)匯編

swift_allocObject會(huì)在堆中開辟一段新的空間。
理解
我們可以把閉包想象成一個(gè)類的實(shí)例對(duì)象
- 內(nèi)存在堆空間中
- 捕獲的局部變量或者常量就是對(duì)象的成員(存儲(chǔ)屬性)
- 組成閉包的函數(shù)就是類內(nèi)部定義的方法
我們可以把上面方法想象成這樣
class Closure {
var num = 0
func plus (_ i:Int) -> Int{
num += i
return num
}
}
var cs = Closure()
cs.plus(1)
cs.plus(2)
cs.plus(3)
cs.plus(4)
【注意】
1、作為一種優(yōu)化,如果一個(gè)值沒有改變或者在閉包的外面,Swift 可能會(huì)使用這個(gè)值的拷貝而不是捕獲。
2、如果你分配了一個(gè)閉包給類實(shí)例的屬性,并且閉包通過引用該實(shí)例或者它的成員來捕獲實(shí)例,你將在閉包和實(shí)例間建立一個(gè)強(qiáng)引用環(huán)。
3、閉包是引用類型
練習(xí)2
typealias Fn = (Int) -> (Int,Int)
func getFn() -> (Fn,Fn) {
var num1 = 0
var num2 = 0
func plus (_ i:Int) -> (Int,Int){
num1 += i
num2 += i * 2
return (num1,num2)
}
func minus (_ i:Int) -> (Int,Int){
num1 -= i
num2 -= i * 2
return (num1,num2)
}
return (plus,minus)
}
let (p,m) = getFn()
print(p(5)) //(5, 10)
print(m(4)) //(1, 2)
print(p(3)) //(4, 8)
print(m(2)) //(2, 4)
在一個(gè)函數(shù)里面,閉包捕獲值的地址是相同的
我是使用類對(duì)比
class Closure {
var num1 = 0
var num2 = 0
func plus (_ i:Int) -> (Int,Int){
num1 += i
num2 += i * 2
return (num1,num2)
}
func minus (_ i:Int) -> (Int,Int){
num1 -= i
num2 -= i * 2
return (num1,num2)
}
}
var cs = Closure()
print(cs.plus(5)) //(5, 10)
print(cs.minus(4)) //(1, 2)
print(cs.plus(3)) //(4, 8)
print(cs.minus(2)) //(2, 4)
練習(xí)2
var functions:[() -> Int] = []
for i in 1...3{
functions.append{i}
}
for f in functions {
print(f())
}
//1
//2
//3
對(duì)比類
class Closure {
var i:Int
init(_ i:Int) {
self.i = i
}
func get() -> Int {
return i
}
}
var cs:[Closure] = []
for i in 1...3{
cs.append(Closure(i))
}
for cls in cs{
print(cls.get())
}
逃逸閉包(@escaping )與非逃逸閉包(@noescaping)
逃逸閉包(@escaping )
當(dāng)閉包作為一個(gè)實(shí)際參數(shù)傳遞給一個(gè)函數(shù)的時(shí)候,我們就說這個(gè)閉包逃逸了,因?yàn)樗窃诤瘮?shù)返回之后調(diào)用的。當(dāng)你聲明一個(gè)接受閉包作為形式參數(shù)的函數(shù)時(shí),你可以在形式參數(shù)前寫 @escaping 來明確閉包是允許逃逸的。
閉包可以逃逸的一種方法是被儲(chǔ)存在定義于函數(shù)外的變量里。比如說,很多函數(shù)接收閉包實(shí)際參數(shù)來作為啟動(dòng)異步任務(wù)的回調(diào)。函數(shù)在啟動(dòng)任務(wù)后返回,但是閉包要直到任務(wù)完成——閉包需要逃逸,以便于稍后調(diào)用
例如:當(dāng)網(wǎng)絡(luò)請(qǐng)求結(jié)束后調(diào)用的閉包。發(fā)起請(qǐng)求后過了一段時(shí)間后這個(gè)閉包才執(zhí)行,并不一定是在函數(shù)作用域內(nèi)執(zhí)行的
override func viewDidLoad() {
super.viewDidLoad()
getData { (data) in
print("閉包返回結(jié)果:\(data)")
}
}
func getData(closure:@escaping (Any) -> Void) {
print("函數(shù)開始執(zhí)行--\(Thread.current)")
DispatchQueue.global().async {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
print("執(zhí)行了閉包---\(Thread.current)")
closure("345")
})
}
print("函數(shù)執(zhí)行結(jié)束---\(Thread.current)")
}

從結(jié)果可以看出,逃逸閉包的生命周期是長(zhǎng)于函數(shù)的。
逃逸閉包的生命周期:
- 1、閉包作為參數(shù)傳遞給函數(shù);
- 2、退出函數(shù);
- 3、閉包被調(diào)用,閉包生命周期結(jié)束
即逃逸閉包的生命周期長(zhǎng)于函數(shù),函數(shù)退出的時(shí)候,逃逸閉包的引用仍被其他對(duì)象持有,不會(huì)在函數(shù)結(jié)束時(shí)釋放。
非逃逸閉包(@noescaping)
一個(gè)接受閉包作為參數(shù)的函數(shù), 閉包是在這個(gè)函數(shù)結(jié)束前內(nèi)被調(diào)用。
override func viewDidLoad() {
super.viewDidLoad()
handleData { (data) in
print("閉包返回結(jié)果:\(data)")
}
}
func handleData(closure:(Any) -> Void) {
print("函數(shù)開始執(zhí)行--\(Thread.current)")
print("執(zhí)行了閉包---\(Thread.current)")
closure("123")
print("函數(shù)執(zhí)行結(jié)束---\(Thread.current)")
}

為什么要分逃逸閉包和非逃逸閉包
為了管理內(nèi)存,閉包會(huì)強(qiáng)引用它捕獲的所有對(duì)象,比如你在閉包中訪問了當(dāng)前控制器的屬性、函數(shù),編譯器會(huì)要求你在閉包中顯示 self 的引用,這樣閉包會(huì)持有當(dāng)前對(duì)象,容易導(dǎo)致循環(huán)引用。
非逃逸閉包不會(huì)產(chǎn)生循環(huán)引用,它會(huì)在函數(shù)作用域內(nèi)釋放,編譯器可以保證在函數(shù)結(jié)束時(shí)閉包會(huì)釋放它捕獲的所有對(duì)象;使用非逃逸閉包的另一個(gè)好處是編譯器可以應(yīng)用更多強(qiáng)有力的性能優(yōu)化,例如,當(dāng)明確了一個(gè)閉包的生命周期的話,就可以省去一些保留(retain)和釋放(release)的調(diào)用;此外非逃逸閉包它的上下文的內(nèi)存可以保存在棧上而不是堆上。
自動(dòng)閉包
自動(dòng)閉包是一種自動(dòng)創(chuàng)建的用來把作為實(shí)際參數(shù)傳遞給函數(shù)的表達(dá)式打包的閉包。它不接受任何實(shí)際參數(shù),并且當(dāng)它被調(diào)用時(shí),它會(huì)返回內(nèi)部打包的表達(dá)式的值
這個(gè)語(yǔ)法的好處在于通過寫普通表達(dá)式代替顯式閉包而使你省略包圍函數(shù)形式參數(shù)的括號(hào)。
func getFirstPositive1(_ v1:Int, _ v2:Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirstPositive1(1, 2)
func getFirstPositive2(_ v1:Int, _ v2:() -> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
getFirstPositive2(1, 2) //這個(gè)報(bào)錯(cuò)
getFirstPositive2(1, {2})
func getFirstPositive3(_ v1:Int, _ v2:@autoclosure () -> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
getFirstPositive3(1, 2)

- @autoclosure會(huì)自動(dòng)的將
2封裝為{2} - @autoclosure只支持
() -> T的格式參數(shù) - 其中
??就是一個(gè)@autoclosure
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T