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

關注倉庫,及時得到更新:iOS-Source-Code-Analyzehtml

Follow: Draveness · Githubgit

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

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

若是你已經很是瞭解 defer 的做用,你能夠跳過第一部分的內容,直接看 Variable Attributes安全

關於 defer

defer 是 Swift 在 2.0 時代加入的一個關鍵字,它提供了一種很是安全而且簡單的方法聲明一個在做用域結束時執行的代碼塊。app

若是你在 Swift Playground 中輸入如下代碼:框架

func hello() {
    defer {
        print("4")
    }
    if true {
        defer {
            print("2")
        }
        defer {
            print("1")
        }
    }
    print("3")
}

hello()

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

1
2
3
4

你能夠仔細思考一下爲何會有這樣的輸出,並在 Playground 使用 defer 寫一些簡單的代碼,相信你能夠很快理解它是如何工做的。函數

若是對 defer 的做用仍然不是很是瞭解,能夠看 guard & defer 這篇文章的後半部分。學習

Variable Attributes

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

一樣在 GCC 中也存在用於修飾函數的 Function Attributes

Variable Attributes 實際上是 GCC 中用於描述變量的一種修飾符。咱們可使用 __attribute__ 來修飾一些變量來參與靜態分析等編譯過程;而在 Cocoa Touch 中不少的宏其實都是經過 __attribute__ 來實現的,例如:

#define NS_ROOT_CLASS __attribute__((objc_root_class))

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 中必須傳入只有一個參數的函數而且這個參數須要與變量的類型兼容

若是上面這句比較繞口的話很難理解,能夠經過一個簡單的例子理解其使用方法:

void cleanup_block(int *a) {
    printf("%d\n", *a);
}

int variable __attribute__((cleanup(cleanup_block))) = 2;

variable 這個變量離開做用域以後,就會自動將這個變量的指針傳入 cleanup_block 中,調用 cleanup_block 方法來進行『清理』工做。

實現 defer

到目前爲止已經有了實現 defer 須要的所有知識,咱們能夠開始分析 libextobjc 是怎麼作的。

在 libextobjc 中並無使用 defer 這個名字,而是使用了 onExit(表示代碼是在退出做用域時執行)

爲了使 onExit 在使用時更加明顯,libextobjc 經過一些其它的手段使得咱們在每次使用 onExit 時都須要添加一個 @ 符號。

{
    @onExit {
        NSLog("Log when out of scope.");
    };
    NSLog("Log before out of scope.");
}

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

#define onExit \
    ext_keywordify \
    __strong ext_cleanupBlock_t metamacro_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^

既然它只是一個宏,那麼上面的代碼實際上是能夠展開的:

autoreleasepool {}
__strong ext_cleanupBlock_t ext_exitBlock_19 __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^ {
    NSLog("Log when out of scope.");
};

這裏,咱們分幾個部分來分析上面的代碼片斷是如何實現 defer 的功能的:

  1. ext_keywordify 也是一個宏定義,它經過添加在宏以前添加 autoreleasepool {} 強迫 onExit 前必須加上 @ 符號。

    #define ext_keywordify autoreleasepool {}
  2. ext_cleanupBlock_t 是一個類型:

    typedef void (^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

    void ext_executeCleanupBlock (__strong ext_cleanupBlock_t *block) {
        (*block)();
    }

    這個函數的做用只是簡單的執行傳入的 block,它知足了 GCC 文檔中對 cleanup 函數的幾個要求:

    1. 只能包含一個參數

    2. 參數的類型是一個指向變量類型的指針

    3. 函數的返回值是 void

總結

這是分析 libextobjc 框架的第一篇文章,也是比較簡短的一篇,由於咱們在平常開發中基本上用不到這個框架提供的 API,可是它依然會爲咱們展現了不少編程上的黑魔法。

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

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

Follow: Draveness · Github

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

相關文章
相關標籤/搜索