swift 的 defer 幾個簡單的使用場景

準備把 swift 文檔再掃一遍,發現了defer這個關鍵字,恕本人愚鈍,之前還歷來沒有用過這個呢~ 簡單地列一下這個東西有哪些能夠用得上的情景吧~~objective-c

defer 是幹什麼用的

很簡單,用一句話歸納,就是 defer block 裏的代碼會在函數 return 以前執行,不管函數是從哪一個分支 return 的,仍是有 throw,仍是天然而然走到最後一行。swift

這個關鍵字就跟 Java 裏的 try-catch-finally 的finally同樣,無論 try catch 走哪一個分支,它都會在函數 return 以前執行。並且它比 Java 的finally還更強大的一點是,它能夠獨立於 try catch 存在,因此它也能夠成爲整理函數流程的一個小幫手。在函數 return 以前不管如何都要作的處理,能夠放進這個 block 裏,讓代碼看起來更乾淨一些~bash

下面是 swift 文檔上的例子:ide

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
 
func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }
    
    let result = fridgeContent.contains(food)
    return result
}
fridgeContains("banana")
print(fridgeIsOpen)
複製代碼

這個例子裏執行的順序是,先fridgeIsOpen = true,而後是函數體正常的流程,最後在 return 以前執行 fridgeIsOpen = false函數

幾個簡單的使用場景

try catch 結構

最典型的場景,我想也是 defer 這個關鍵字誕生的主要緣由吧:ui

func foo() {
  defer {
    print("finally")
  }
  do {
    throw NSError()
    print("impossible")
  } catch {
    print("handle error")
  }
}
複製代碼

無論 do block 是否 throw error,有沒有 catch 到,仍是 throw 出去了,都會保證在整個函數 return 前執行 defer。在這個例子裏,就是先 print 出 "handle error" 再 print 出 "finally"。url

do block 裏也能夠寫 deferspa

do {
  defer {
    print("finally")
  }
  throw NSError()
  print("impossible")
} catch {
  print("handle error")
}
複製代碼

那麼它執行的順序就會是在 catch block 以前,也就是先 print 出 "finally" 再 print 出 "handle error"。code

清理工做、回收資源

跟 swift 文檔舉的例子相似,defer一個很適合的使用場景就是用來作清理工做。文件操做就是一個很好的例子:ip

關閉文件

func foo() {
  let fileDescriptor = open(url.path, O_EVTONLY)
  defer {
    close(fileDescriptor)
  }
  // use fileDescriptor...
}
複製代碼

這樣就不怕哪一個分支忘了寫,或者中間 throw 個 error,致使 fileDescriptor 無法正常關閉。還有一些相似的場景:

dealloc 手動分配的空間

func foo() {
  let valuePointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
  defer {
    valuePointer.deallocate(capacity: 1)
  }
  // use pointer...
}
複製代碼

加/解鎖:下面是 swift 裏相似 Objective-C 的 synchronized block 的一種寫法,可使用任何一個 NSObject 做 lock

func foo() {
  objc_sync_enter(lock)
  defer { 
    objc_sync_exit(lock)
  }
  // do something...
}
複製代碼

像這種成對調用的方法,能夠用 defer 把它們放在一塊兒,一目瞭然。

調 completion block

這是一個讓我感受「若是當時知道 defer 」就行了的場景,就是有時候一個函數分支比較多,可能某個小分支 return 以前就忘了調 completion block,結果藏下一個不易發現的 bug。用 defer 就能夠不用擔憂這個問題了:

func foo(completion: () -> Void) {
  defer {
    self.isLoading = false
    completion()
  }
  guard error == nil else { return } 
  // handle success
}
複製代碼

有時候 completion 要根據狀況傳不一樣的參數,這時 defer 就很差使了。不過若是 completion block 被存下來了,咱們仍是能夠用它來確保執行後能釋放:

func foo() {
  defer {
    self.completion = nil
  }
  if (succeed) {
    self.completion(.success(result))
  } else {
    self.completion(.error(error))
  }
}
複製代碼

調 super 方法

有時候 override 一個方法,主要目的是在 super 方法以前作一些準備工做,好比 UICollectionViewLayoutprepare(forCollectionViewUpdates:),那麼咱們就能夠把調用 super 的部分放在 defer 裏:

func override foo() {
  defer {
    super.foo()
  }
  // some preparation before super.foo()...
}
複製代碼

一些細節

任意 scope 均可以有 defer

雖然大部分的使用場景是在函數裏,不過理論上任何一個 { } 之間都是能夠寫 defer 的。好比一個普通的循環:

var sumOfOdd = 0
for i in 0...10 {
  defer {
    print("Look! It's \(i)")
  }
  if i % 2 == 0 {
    continue
  }
  sumOfOdd += i
}
複製代碼

continue 或者 break 都不會妨礙 defer 的執行。甚至一個無緣無故的 closure 裏也能夠寫 defer

{
  defer { print("bye!") }
  print("hello!")
}
複製代碼

就是這樣沒什麼意義就是了……

必須執行到 defer 纔會觸發

假設有這樣一個問題:一個 scope 裏的 defer 能保證必定會執行嗎? 答案是否……好比下面這個例子:

func foo() throws {
  do {
    throw NSError()
    print("impossible")
  }
  defer {
    print("finally")
  }
}
try?foo()
複製代碼

不會執行 defer,不會 print 任何東西。這個故事告訴咱們,至少要執行到 defer 這一行,它才保證後面會觸發。一樣道理,提早 return 也是同樣不行的:

func foo() {
  guard false else { return }
  defer {
    print("finally")
  }
}
複製代碼

多個 defer

一個 scope 能夠有多個 defer,順序是像棧同樣倒着執行的:每遇到一個 defer 就像壓進一個棧裏,到 scope 結束的時候,後進棧的先執行。以下面的代碼,會按 一、二、三、四、五、6 的順序 print 出來。

func foo() {
  print("1")
  defer {
    print("6")
  }
  print("2")
  defer {
    print("5")
  }
  print("3")
  defer {
    print("4")
  }
}
複製代碼

可是我強烈建議不要這麼寫。我是建議一個 scope 裏不要有多個 defer,感受除了讓讀代碼的人感受混亂以外沒有什麼好處。

參考資料

What is the Swift equivalent to Objective-C's 「@synchronized」?

相關文章
相關標籤/搜索