如何在 Objective-C 的環境下實現 defer

發表於2016-07-20

關注倉庫,及時獲得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

這篇文章會對 libextobjc 中的一小部分程式碼進行分析,也是如何擴充套件 Objective-C 語言系列文章的第一篇,筆者會從 libextobjc 中選擇一些黑魔法進行介紹。

對 Swift 稍有了解的人都知道,defer 在 Swift 語言中是一個關鍵字;在 defer 程式碼塊中的程式碼,會在作用域結束時執行。在這裡,我們會使用一些神奇的方法在 Objective-C 中實現 defer

如果你已經非常瞭解 defer 的作用,你可以跳過第一部分的內容,直接看 Variable Attributes

關於 defer

defer 是 Swift 在 2.0 時代加入的一個關鍵字,它提供了一種非常安全並且簡單的方法宣告一個在作用域結束時執行的程式碼塊。

如果你在 Swift Playground 中輸入以下程式碼:

控制檯的輸出會是這樣的:

你可以仔細思考一下為什麼會有這樣的輸出,並在 Playground 使用 defer 寫一些簡單的程式碼,相信你可以很快理解它是如何工作的。

如果對 defer 的作用仍然不是非常瞭解,可以看 guard & defer 這篇文章的後半部分。

Variable Attributes

libextobjc 實現的 defer 並沒有基於 Objective-C 的動態特性,甚至也沒有呼叫已有的任何方法,而是使用了 Variable Attributes 這一特性。

同樣在 GCC 中也存在用於修飾函式的 Function Attributes

Variable Attributes 其實是 GCC 中用於描述變數的一種修飾符。我們可以使用 __attribute__ 來修飾一些變數來參與靜態分析等編譯過程;而在 Cocoa Touch 中很多的巨集其實都是通過 __attribute__ 來實現的,例如:

cleanup 就是在這裡會使用的變數屬性:

The cleanup attribute runs a function when the variable goes out of scope. This attribute can only be applied to auto function scope variables; it may not be applied to parameters or variables with static storage duration. The function must take one parameter, a pointer to a type compatible with the variable. The return value of the function (if any) is ignored.

GCC 文件中對 cleanup 屬性的介紹告訴我們,在 cleanup 中必須傳入只有一個引數的函式並且這個引數需要與變數的型別相容

如果上面這句比較繞口的話很難理解,可以通過一個簡單的例子理解其使用方法:

variable 這個變數離開作用域之後,就會自動將這個變數的指標傳入 cleanup_block 中,呼叫 cleanup_block 方法來進行『清理』工作。

實現 defer

到目前為止已經有了實現 defer 需要的全部知識,我們可以開始分析 libextobjc 是怎麼做的。

在 libextobjc 中並沒有使用 defer 這個名字,而是使用了 onExit(表示程式碼是在退出作用域時執行)

為了使 onExit 在使用時更加明顯,libextobjc 通過一些其它的手段使得我們在每次使用 onExit 時都需要新增一個 @ 符號。

onExit 其實只是一個精心設計的巨集:

既然它只是一個巨集,那麼上面的程式碼其實是可以展開的:

這裡,我們分幾個部分來分析上面的程式碼片段是如何實現 defer 的功能的:

  1. ext_keywordify 也是一個巨集定義,它通過新增在巨集之前新增 autoreleasepool {} 強迫 onExit 前必須加上 @ 符號。
  2. ext_cleanupBlock_t 是一個型別:
  3. metamacro_concat(ext_exitBlock_, __LINE__) 會將 ext_exitBlock 和當前行號拼接成一個臨時的的變數名,例如:ext_exitBlock_19
  4. __attribute__((cleanup(ext_executeCleanupBlock), unused))cleanup 函式設定為 ext_executeCleanupBlock;並將當前變數 ext_exitBlock_19 標記為 unused 來抑制 Unused variable 警告。
  5. 變數 ext_exitBlock_19 的值為 ^{ NSLog("Log when out of scope."); },是一個型別為 ext_cleanupBlock_t 的 block。
  6. 在這個變數離開作用域時,會把上面的 block 的指標傳入 cleanup 函式,也就是 ext_executeCleanupBlock

    這個函式的作用只是簡單的執行傳入的 block,它滿足了 GCC 文件中對 cleanup 函式的幾個要求:
    1. 只能包含一個引數
    2. 引數的型別是一個指向變數型別的指標
    3. 函式的返回值是 void

總結

這是分析 libextobjc 框架的第一篇文章,也是比較簡短的一篇,因為我們在日常開發中基本上用不到這個框架提供的 API,但是它依然會為我們展示了很多程式設計上的黑魔法。

libextobjc 將 cleanup 這一變數屬性,很好地包裝成了 @onExit,它的實現也是比較有意思的,也激起了筆者學習 GCC 編譯命令並且閱讀一些文件的想法。

關注倉庫,及時獲得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

原文連結: https://draveness.me/defer

相關文章