關注倉庫,及時獲得更新: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 中輸入以下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func hello() { defer { print("4") } if true { defer { print("2") } defer { print("1") } } print("3") } hello() |
控制檯的輸出會是這樣的:
1 2 3 4 |
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__
來實現的,例如:
1 |
#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
中必須傳入只有一個引數的函式並且這個引數需要與變數的型別相容。
如果上面這句比較繞口的話很難理解,可以通過一個簡單的例子理解其使用方法:
1 2 3 4 5 |
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
時都需要新增一個@
符號。
1 2 3 4 5 6 |
{ @onExit { NSLog("Log when out of scope."); }; NSLog("Log before out of scope."); } |
onExit
其實只是一個精心設計的巨集:
1 2 3 |
#define onExit \ ext_keywordify \ __strong ext_cleanupBlock_t metamacro_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^ |
既然它只是一個巨集,那麼上面的程式碼其實是可以展開的:
1 2 3 4 |
autoreleasepool {} __strong ext_cleanupBlock_t ext_exitBlock_19 __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^ { NSLog("Log when out of scope."); }; |
這裡,我們分幾個部分來分析上面的程式碼片段是如何實現 defer
的功能的:
ext_keywordify
也是一個巨集定義,它通過新增在巨集之前新增autoreleasepool {}
強迫onExit
前必須加上@
符號。
1#define ext_keywordify autoreleasepool {}ext_cleanupBlock_t
是一個型別:
1typedef void (^ext_cleanupBlock_t)();metamacro_concat(ext_exitBlock_, __LINE__)
會將ext_exitBlock
和當前行號拼接成一個臨時的的變數名,例如:ext_exitBlock_19
。__attribute__((cleanup(ext_executeCleanupBlock), unused))
將cleanup
函式設定為ext_executeCleanupBlock
;並將當前變數ext_exitBlock_19
標記為unused
來抑制Unused variable
警告。- 變數
ext_exitBlock_19
的值為^{ NSLog("Log when out of scope."); }
,是一個型別為ext_cleanupBlock_t
的 block。 - 在這個變數離開作用域時,會把上面的 block 的指標傳入
cleanup
函式,也就是ext_executeCleanupBlock
:
123void ext_executeCleanupBlock (__strong ext_cleanupBlock_t *block) {(*block)();}
這個函式的作用只是簡單的執行傳入的 block,它滿足了 GCC 文件中對cleanup
函式的幾個要求:- 只能包含一個引數
- 引數的型別是一個指向變數型別的指標
- 函式的返回值是
void
總結
這是分析 libextobjc 框架的第一篇文章,也是比較簡短的一篇,因為我們在日常開發中基本上用不到這個框架提供的 API,但是它依然會為我們展示了很多程式設計上的黑魔法。
libextobjc 將 cleanup
這一變數屬性,很好地包裝成了 @onExit
,它的實現也是比較有意思的,也激起了筆者學習 GCC 編譯命令並且閱讀一些文件的想法。
關注倉庫,及時獲得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github