Intro
在 Objective-C 中我們可以用 @"foo"
來建立一個 NSString
常量,看起來似乎平淡無奇。
但它背後其實比想象的精彩,@
可以被理解成一個特殊的巨集,其接受一個 C 字串作為引數,也可寫作 @("foo")
。
之所以說 @
是一個特殊的巨集,是因為其能根據傳入的 C 字串型別不同——C 字串常量或 C 字串——在執行時構建返回不同型別的 NSString
,參見下面的程式碼:
1 2 3 4 5 6 7 8 |
char* obtain_c_string(void) { return "c_string"; } NSLog(@"%@", @"foo".class); NSLog(@"%@", @("bar").class); NSLog(@"%@", @(obtain_c_string()).class); |
輸出結果如下:
1 2 3 |
2013-06-05 01:14:15.097 Sandbox[45804:c07] __NSCFConstantString 2013-06-05 01:14:15.098 Sandbox[45804:c07] __NSCFConstantString 2013-06-05 01:14:15.098 Sandbox[45804:c07] __NSCFString |
可見,如果傳入的是 C 字串常量,執行時構建的則為 NSConstantString
;如果傳入的是 C 字串則建立的是 NSString
。
Then?
你可能會問這麼理解了又怎樣?
眾所周知,Objective-C 程式碼裡有很多地方需要我們把程式碼中的一些文法串寫成字串再作為傳入引數,比如 KVO 中的 keyPath
引數往往就要傳入形如 propertyA.propertyB
的字串,從實用角度出發這有兩個弊端:
- 寫字串的時候沒有程式碼提示,很容易寫錯
- 即便一開始寫對了,如果後來相關類重構了,
keyPath
的引數便失效了,而 Xcode Refactor 無法掃描字串
當我們理解了 @()
,再加上自定義的巨集,上述兩個問題便可迎刃而解。
1 2 3 4 5 6 7 8 9 |
/** * # 將巨集的引數字串化,C 函式 strchr 返回字串中第一個 '.' 字元的位置 */ #define Keypath(keypath) (strchr(#keypath, '.') + 1) [objA addObserver:objB forKeyPath:@Keypath(ObjA.property1.property2) // 有程式碼提示,可以被重構掃描到 options:nil context:nil]; |
這個簡單實現只算是拋磚引玉,除了 @()
配合自定義巨集來字串化程式碼中的文法串,更多的用法就有待在開發中不斷髮掘了。
PS: 在即將完成這篇文章的時候我發現已有國外開發者利用 @()
特性配合自定義巨集,全面系統的解決了上述問題,詳情參見 libextobjc/EXTKeyPathCoding.h。
Extra
此外,@()
還可以接受 int 字面量或 int 變數作為引數,有興趣的讀者可以自行感受下。