1. 程式人生 > 程式設計 >swift中defer的實際應用小結

swift中defer的實際應用小結

看看蘋果官方的介紹

用 defer 語句在即將離開當前程式碼塊時執行一系列語句。該語句讓你能執行一些必要的清理工作,不管是以何種方式離開當前程式碼塊的——無論是由於丟擲錯誤而離開,或是由於諸如 return、break 的語句。例如,你可以用 defer 語句來確保檔案描述符得以關閉,以及手動分配的記憶體得以釋放。
defer 語句將程式碼的執行延遲到當前的作用域退出之前。該語句由 defer 關鍵字和要被延遲執行的語句組成。延遲執行的語句不能包含任何控制轉移語句,例如 break、return 語句,或是丟擲一個錯誤。延遲執行的操作會按照它們宣告的順序從後往前執行——也就是說,第一條 defer 語句中的程式碼最後才執行,第二條 defer 語句中的程式碼倒數第二個執行,以此類推。最後一條語句會第一個執行。

總結一下蘋果官方的介紹

defer語句在程式碼塊(方法、閉包等,可以理解為大括號包裝起來的程式碼)作用域退出之前\color{red}{作用域退出之前}作用域退出之前執行,也就是程式碼塊中其他應該執行的程式碼都執行完了,才執行defer中的程式碼
一個程式碼塊允許多個defer,多個defer執行的順序從後到前\color{red}{從後到前}從後到前

一些測試及誤區糾正

測試案例1

func testDefer() {
 defer {
 print("方法中defer內容")
 }
 if true {
 defer {
  print("if 中defer內容")
 }
 print("if中最後的程式碼")
 }
 print("方法中的程式碼")
 if true {
 return
 }
 print("方法結束前最後一句程式碼")
}
testDefer()

以上程式碼列印結果:

if中最後的程式碼
if 中defer內容
方法中的程式碼
方法中defer內容

列印結果中,第一個if中的程式碼及裡面的defer最先執行,方法中的defer最後執行,由此可以看出,程式碼塊中其他能夠執行的程式碼先執行,最後執行defer的內容;defer的作用範圍不能簡單的看成方法,而是程式碼塊(可能有些同學會有這樣的誤區)

測試案例2

func testDefer() {
 print("開始")
 defer {
 print("defer 1 中的內容")
 }
 defer {
 print("defer 2 中的內容")
 }
 if true {
 return
 }
 defer {
 print("defer 3 中的內容")
 }
 print("方法結束前最後一句程式碼")
}
testDefer()

列印結果

開始
defer 2 中的內容
defer 1 中的內容

我們可以看到最後一個defer沒有執行,所以defer定義的位置很重要,如果沒有執行defer定義的程式碼,在程式碼塊結束前不會執行defer中的內容

多個defer的執行順序從後到前

一些實際應用場景

場景1:一些資源用完後需釋放,這裡給的是官方的一個案例

func processFile(filename: String) throws {
 if exists(filename) {
 let file = open(filename)
 defer {
  close(file)
 }
 while let line = try file.readline() {
  // 處理檔案。
 }
 // close(file) 會在這裡被呼叫,即作用域的最後。
 }
}

開始用到資源的時候就使用defer去釋放,避免忘記釋放資源

場景2:加鎖解鎖,借鑑了kingfisher

let lock = NSLock()
func testDefer() {
 lock.lock()
 defer {
 lock.unlock()
 }
 
 doSomething()
}
testDefer()

在加鎖後立刻用defer解鎖,避免忘記解鎖

場景3:處理一些程式碼塊作用域結束前的重複操作,比如請求網路資料的時候

通常的一種寫法

func loadCityList(_ finish: ((Error?,[String]?) -> ())?) {
 DispatchQueue.global().async { // 模擬網路請求
 let data: AnyObject? // 模擬伺服器返回的資料
 guard let dict = data as? [String: AnyObject] else {
  DispatchQueue.main.async {
  finish?(error,nil)
  }
  return
 }
 guard let code = dict["code"] as? Int,code == 200 else {
  DispatchQueue.main.async {
  finish?(error,nil)
  }
  return
 }
 guard let citys = dict["data"] as? [String]? else {
  DispatchQueue.main.async {
  finish?(error,nil)
  }
  return
 }
 DispatchQueue.main.async {
  finish?(nil,citys)
 }
 }
}

當每次有錯誤處理時和結果正確時都需要去做回撥,而且回撥可能有一堆程式碼,看起來程式碼會比較冗餘,而且在一些錯誤處理時很容易造成忘記回撥

defer怎麼去寫呢

func loadCityList(_ finish: ((Error?,[String]?) -> ())?) {
 DispatchQueue.global().async { // 模擬網路請求
 var error: Error? = nil
 var citys: [String]? = nil
 defer {
  DispatchQueue.main.async {
  finish?(error,citys)
  }
 }
 
 let data: AnyObject? // 模擬伺服器返回的資料
 guard let dict = data as? [String: AnyObject] else {
  error = ...
  return
 }
 guard let code = dict["code"] as? Int,code == 200 else {
  error = ...
  return
 }
 guard let tempCitys = dict["data"] as? [String]? else {
  error = ...
  return
 }
 citys = tempCitys
 }
}

使用defer既解決了程式碼冗餘,又解決了可能忘記回撥的問題,還有當我們看到defer時,我們很清楚知道,無論網路請求結果如果,都會回撥

總結

本文主要介紹了defer的定義、作用及一些用法

到此這篇關於swift中defer的實際應用的文章就介紹到這了,更多相關swift中defer應用內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!