Swift-逃逸閉包蟆炊、自動閉包

閉包是引用類型

下面的例子中堕澄,incrementBySevenincrementByTen 都是常量僵芹,但是這些常量指向的閉包仍然可以增加其捕獲的變量的值处硬。這是因為函數(shù)和閉包都是引用類型

無論你將函數(shù)或閉包賦值給一個常量還是變量拇派,你實際上都是將常量或變量的值設(shè)置為對應(yīng)函數(shù)或閉包的引用荷辕。上面的例子中,指向閉包的引用 incrementByTenincrementBySeven 是一個常量件豌,而并非閉包內(nèi)容本身疮方。

這也意味著如果你將閉包賦值給了兩個不同的常量或變量,兩個值都會指向同一個閉包:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

let incrementBySeven = makeIncrementer(forIncrement: 10)
let incrementByTen = makeIncrementer(forIncrement: 20)

incrementBySeven()
// 返回的值為10
incrementBySeven()
// 返回的值為20

incrementByTen()
// 返回的值為20
incrementByTen()
// 返回的值為40

var incrementByTenThree = makeIncrementer(forIncrement: 15)
incrementByTenThree()
// 返回的值為15
incrementByTenThree()
// 返回的值為30

逃逸閉包

當(dāng)一個閉包作為參數(shù)傳到一個函數(shù)中茧彤,但是這個閉包在函數(shù)返回之后才被執(zhí)行骡显,我們稱該閉包從函數(shù)中逃逸。當(dāng)你定義接受閉包作為參數(shù)的函數(shù)時曾掂,你可以在參數(shù)名之前標(biāo)注 @escaping惫谤,用來指明這個閉包是允許“逃逸”出這個函數(shù)的。

一種能使閉包“逃逸”出函數(shù)的方法是珠洗,將這個閉包保存在一個函數(shù)外部定義的變量中溜歪。舉個例子,很多啟動異步操作的函數(shù)接受一個閉包參數(shù)作為 completion handler许蓖。這類函數(shù)會在異步操作開始之后立刻返回蝴猪,但是閉包直到異步操作結(jié)束后才會被調(diào)用。在這種情況下膊爪,閉包需要“逃逸”出函數(shù)自阱,因為閉包需要在函數(shù)返回之后被調(diào)用。例如:

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

someFunctionWithEscapingClosure(_:) 函數(shù)接受一個閉包作為參數(shù)米酬,該閉包被添加到一個函數(shù)外定義的數(shù)組中沛豌。如果你不將這個參數(shù)標(biāo)記為 @escaping,就會得到一個編譯錯誤淮逻。

將一個閉包標(biāo)記為 @escaping 意味著你必須在閉包中顯式地引用 self琼懊。比如說,在下面的代碼中爬早,傳遞到 someFunctionWithEscapingClosure(:) 中的閉包是一個逃逸閉包,這意味著它需要顯式地引用 self启妹。相對的筛严,傳遞到 someFunctionWithNonescapingClosure(:) 中的閉包是一個非逃逸閉包,這意味著它可以隱式引用 self饶米。

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出 "200"

completionHandlers.first?()
print(instance.x)
// 打印出 "100"

自動閉包

自動閉包是一種自動創(chuàng)建的閉包桨啃,用于包裝傳遞給函數(shù)作為參數(shù)的表達(dá)式车胡。這種閉包不接受任何參數(shù),當(dāng)它被調(diào)用的時候照瘾,會返回被包裝在其中的表達(dá)式的值匈棘。這種便利語法讓你能夠省略閉包的花括號,用一個普通的表達(dá)式來代替顯式的閉包析命。

我們經(jīng)常會調(diào)用采用自動閉包的函數(shù)主卫,但是很少去實現(xiàn)這樣的函數(shù)。舉個例子來說鹃愤,assert(condition:message:file:line:) 函數(shù)接受自動閉包作為它的 condition 參數(shù)和 message 參數(shù)簇搅;它的 condition 參數(shù)僅會在 debug 模式下被求值,它的 message 參數(shù)僅當(dāng) condition 參數(shù)為 false 時被計算求值软吐。

自動閉包讓你能夠延遲求值瘩将,因為直到你調(diào)用這個閉包,代碼段才會被執(zhí)行凹耙。延遲求值對于那些有副作用(Side Effect)和高計算成本的代碼來說是很有益處的姿现,因為它使得你能控制代碼的執(zhí)行時機(jī)。下面的代碼展示了閉包如何延時求值肖抱。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 打印出 "5"

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"

盡管在閉包的代碼中备典,customersInLine的第一個元素被移除了,不過在閉包被調(diào)用之前虐沥,這個元素是不會被移除的熊经。如果這個閉包永遠(yuǎn)不被調(diào)用,那么在閉包里面的表達(dá)式將永遠(yuǎn)不會執(zhí)行欲险,那意味著列表中的元素永遠(yuǎn)不會被移除镐依。請注意,customerProvider 的類型不是 String天试,而是 () -> String槐壳,一個沒有參數(shù)且返回值為 String 的函數(shù)。

將閉包作為參數(shù)傳遞給函數(shù)時喜每,你能獲得同樣的延時求值行為务唐。

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出 "Now serving Alex!"

上面的serve(customer:) 函數(shù)接受一個返回顧客名字的顯式的閉包。下面這個版本的 serve(customer:)完成了相同的操作带兜,不過它并沒有接受一個顯式的閉包枫笛,而是通過將參數(shù)標(biāo)記為 @autoclosure 來接收一個自動閉包。現(xiàn)在你可以將該函數(shù)當(dāng)作接受 String 類型參數(shù)(而非閉包)的函數(shù)來調(diào)用刚照。customerProvider 參數(shù)將自動轉(zhuǎn)化為一個閉包刑巧,因為該參數(shù)被標(biāo)記了 @autoclosure特性。

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 打印 "Now serving Ewa!"

注意 過度使用 autoclosures 會讓你的代碼變得難以理解。上下文和函數(shù)名應(yīng)該能夠清晰地表明求值是被延遲執(zhí)行的

如果你想讓一個自動閉包可以“逃逸”啊楚,則應(yīng)該同時使用 @autoclosure@escaping 屬性吠冤。@escaping 屬性的講解見上面的逃逸閉包


// customersInLine i= ["Barry", "Daniella"]/ 
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// 打印 "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// 打印 "Now serving Barry!"
// 打印 "Now serving Daniella!"

在上面的代碼中恭理,collectCustomerProviders(_:)函數(shù)并沒有調(diào)用傳入的customerProvider閉包拯辙,而是將閉包追加到了 customerProviders 數(shù)組中。這個數(shù)組定義在函數(shù)作用域范圍外颜价,這意味著數(shù)組內(nèi)的閉包能夠在函數(shù)返回之后被調(diào)用涯保。因此,customerProvider參數(shù)必須允許“逃逸”出函數(shù)作用域拍嵌。

原文出自51Swift轉(zhuǎn)載請保留原文鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遭赂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子横辆,更是在濱河造成了極大的恐慌撇他,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狈蚤,死亡現(xiàn)場離奇詭異困肩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)脆侮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門锌畸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人靖避,你說我怎么就攤上這事潭枣。” “怎么了幻捏?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵盆犁,是天一觀的道長。 經(jīng)常有香客問我篡九,道長谐岁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任榛臼,我火速辦了婚禮伊佃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沛善。我一直安慰自己航揉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布金刁。 她就那樣靜靜地躺著迷捧,像睡著了一般织咧。 火紅的嫁衣襯著肌膚如雪胀葱。 梳的紋絲不亂的頭發(fā)上漠秋,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音抵屿,去河邊找鬼庆锦。 笑死,一個胖子當(dāng)著我的面吹牛轧葛,可吹牛的內(nèi)容都是我干的搂抒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尿扯,長吁一口氣:“原來是場噩夢啊……” “哼求晶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衷笋,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤芳杏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辟宗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爵赵,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年泊脐,在試婚紗的時候發(fā)現(xiàn)自己被綠了空幻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡容客,死狀恐怖秕铛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缩挑,我是刑警寧澤但两,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站调煎,受9級特大地震影響镜遣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜士袄,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一悲关、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧娄柳,春花似錦寓辱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诱鞠。三九已至,卻和暖如春这敬,著一層夾襖步出監(jiān)牢的瞬間航夺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工崔涂, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留阳掐,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓冷蚂,卻偏偏與公主長得像缭保,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝙茶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容