Swift 中 defer 的介紹與使用場(chǎng)景
用defer語句在即將離開當(dāng)前代碼塊時(shí)執(zhí)行一系列語句。該語句讓你能執(zhí)行一些必要的善后工作(比如,關(guān)閉,清理,回調(diào))不管是以何種方式離開當(dāng)前代碼塊的---無論是由于拋出錯(cuò)誤而離開,或者是由于諸如return,break的語句。
例如:你可以用defer語句來確保文件描述符得以關(guān)閉,以及手動(dòng)分配的內(nèi)存得以釋放。
defer語句將代碼的執(zhí)行延遲到當(dāng)前的作用域退出之前。該語句由defer關(guān)鍵字和要被延遲的語句組成。延遲執(zhí)行的語句不能包括任何控制轉(zhuǎn)移語句,例如break,return語句,或是拋出一個(gè)錯(cuò)誤,延遲執(zhí)行的操作會(huì)按照它們聲明的順序從后往前執(zhí)行---也就是說,第一條defer語句中的代碼最后才執(zhí)行,第二條defer語句中的代碼倒數(shù)第二個(gè)執(zhí)行,以此類推,最后一條語句會(huì)第一個(gè)執(zhí)行。
defer 語句在代碼塊(方法,閉包等,可以理解為大括號(hào)包裝起來的代碼)作用域退出之前執(zhí)行,也就是代碼塊中其他代碼該執(zhí)行的都執(zhí)行完了,才執(zhí)行defer中的代碼一個(gè)代碼塊允許多個(gè)defer,多個(gè)defer執(zhí)行的順序從后到前。
測(cè)試案例1
func testDefer() {
defer {
print("方法中defer內(nèi)容") // 4
}
if true {
defer {
print("if 中defer內(nèi)容") // 2
}
print("if中最后的代碼") // 1
}
print("方法中的代碼") // 3
if true {
return
}
print("方法結(jié)束前最后一句代碼") // 前面return 掉了 這里不執(zhí)行
}
testDefer()
執(zhí)行結(jié)果
if中最后的代碼
if 中defer內(nèi)容
方法中的代碼
方法中defer內(nèi)容
打印結(jié)果中,第一個(gè) if true 中的代碼塊先執(zhí)行(if中的 最后的代碼先執(zhí)行,然后是 if中的 defer 代碼塊),然后是 if下方的 方法中的代碼執(zhí)行,最后執(zhí)行testDefer方法中的 defer 代碼塊。
由此可以看出,代碼塊中其他能夠執(zhí)行的代碼先執(zhí)行,最后執(zhí)行defer的內(nèi)容;defer的作用范圍不能簡(jiǎn)單的看成方法,而是代碼塊
測(cè)試案例2
func testDefer() {
print("開始") // 1
defer {
print("defer 1 中的內(nèi)容") // 4
}
defer {
print("defer 2 中的內(nèi)容") // 3
}
print("方法中的代碼") // 2
if true {
return
}
defer {
print("defer 3 中的內(nèi)容")
}
print("方法結(jié)束前最后一句代碼")
}
testDefer()
結(jié)果
開始
方法中的代碼
defer 2 中的內(nèi)容
defer 1 中的內(nèi)容
我們可以看出 if true 之后的代碼 都沒有執(zhí)行,包括最后一個(gè) defer 3 也沒有執(zhí)行,所以defer定義的位置很重要,如果沒有執(zhí)行defer定義的代碼,在代碼塊結(jié)束前,不會(huì)執(zhí)行defer中的內(nèi)容。
注意:多個(gè)defer的執(zhí)行順序從后到前
一些實(shí)際應(yīng)用場(chǎng)景
場(chǎng)景1 - 一些資源用完后需要釋放,這里給的是官方的一個(gè)案例
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 處理文件。
}
// close(file) 會(huì)在這里被調(diào)用,即作用域的最后。
}
}
開始用到資源的時(shí)候就是用defer去釋放,避免忘記釋放資源。
場(chǎng)景2 - 加鎖解鎖,借鑒了 kingfisher
let lock = NSLock()
func testDefer() {
lock.lock()
defer {
lock.unlock()
}
print("doSomething。。。")
}
testDefer()
在加鎖后,添加defer代碼解鎖,避免忘記解鎖。
場(chǎng)景3 - 處理一些代碼塊作用域結(jié)束前的重復(fù)操作,如調(diào) completion block
這是一個(gè)讓我感覺“如果當(dāng)時(shí)知道 defer ”就好了的場(chǎng)景,就是有時(shí)候一個(gè)函數(shù)分支比較多,可能某個(gè)小分支 return 之前就忘了調(diào) completion block,結(jié)果藏下一個(gè)不易發(fā)現(xiàn)的 bug。用 defer 就可以不用擔(dān)心這個(gè)問題了
func foo(completion: () -> Void) {
defer {
self.isLoading = false
completion()
}
guard error == nil else { return }
// handle success
}
有時(shí)候 completion 要根據(jù)情況傳不同的參數(shù),這時(shí) defer 就不好使了。不過如果 completion block 被存下來了,我們還是可以用它來確保執(zhí)行后能釋放:
func foo() {
defer {
self.completion = nil
}
if (succeed) {
self.completion(.success(result))
} else {
self.completion(.error(error))
}
}
場(chǎng)景4 調(diào)用supper方法
有時(shí)候 override 一個(gè)方法, 主要目的是在super方法之前做一些準(zhǔn)備工作,我們就可以把調(diào)用 super 的部分放在 defer 里面:
func override foo() {
defer {
super.foo()
}
// some preparation before super.foo()...
}
建議:最好不要加多個(gè) defer 否則邏輯更加會(huì)顯著比較混亂。
大家多喝熱水,最近天真干。