Item4: Prefer Typed Constants to Preprocessor #define

在工作中, 我們經常會定義常量。 例如:考慮一個場景, 當一個 UIView 的子類,使用動畫進行presentdismiss. 此時也許你會想到一個非常典型的常量就是Animation Duratation. 也許你會對Animation Duration 有如下的定義:

#define ANIMATION_DURATION 0.3

這是一個預編譯指令: 無論何時,在你的源碼中找到了ANIMATION_DURATION字符串, 都會替換成為 0.3. 這看起來好像正是我們想要的效果, 但是這種定義方法沒有類型信息. 看到duration 給人的感覺好像是定義了一個和時間相關的宏, 但是并不明確. 而且預編譯指令將會盲目的替換所有出現的 ANIMATION_DURATION字符串, 所以如果這個宏定義定義在了.h文件中, 那么所有包含這個.h的文件中的ANIMAATION_DURATION字符串, 都會被替換成為0.3.
為了解決這個問題, 這里有一個比預編譯指令更好的方法來定義一個常量, 例如下面定義的這個NSTimeInterval常量

static const NSTimeInterval kAnimationDuration = 0.3;

記住這種格式. 它包含了類型信息, 并且很清晰的定義了這個常量是什么. 數據類型為 NSTimeInterval, 可以很好的幫助你理解這個變量的用處. 如果你又好多定義的常量, 這種方法也會幫助你和你的小伙伴在將來更好的閱讀和理解代碼.
并且你還需要記住這個變量的是如何命名的, 通常我們約定常量都是以小寫字母k 開頭, 并且定義在 .m 文件中. 如果我們定義的這個常量需要暴露在 .h 文件中, 那么我們的常量需要以類名開頭. 在 Item 19將會更詳細的介紹命名規則.
聲明一個常量的位置是非常重要的, 有些時候, 我們會直接聲明一個 #define 預編譯指令在.h文件中, 這是一個非常不好的習慣. 例如: 在.h 中的ANIMATION_DURATION常量就是一個非常不好的命名方式, 它將會出現在所有包含了該.h的文件中. 盡管使用static const 修飾的kAnimationDuration常量的命名相對標準, 但是仍然不應該出現在.h文件中, 因為Objective-C 沒有命名空間, 所以將會定義一個命名為kAnimationDuration 的全局變量. 命名應該加一些前綴, 來局限這個變量是在哪個類中使用, 例如:XXXViewAnimationDuration. Item19將會解釋更多的策略使得命名更加的清晰.
一個常量如果不需要暴露在外, 那么就應該定義在它所在的.m文件中. 例如: 如果Animation Duration 常量被用在了一個 UIView 的子類中, 應該像如下這樣定義:

// XXXAnimationView.h
#import <UIKit/UIKit.h>

@interface XXXAnimationView : UIView
- (void) animate;
@end


// XXXAnimationView.m
#import "XXXAnimationView.h"

static const NSTimeInterval kAnimationDuration = 0.3f;

@implementation XXXAnimationView

- (void) animate {
  [UIView animateWithDuration: kAnimationDuration
                    animation:^(){
                        // Code Here
                    }];
}

@end

使用 staticconst 修飾常量非常重要. const 修飾符意味著如果我們嘗試去修改這個常量的值, 編譯器將會向我們拋出一個錯誤, 因為常量的值是不允許被修改的. 所以對于 常量而言, const 修飾符是必不可少的. static 修飾符意味著這個常量僅在定義的.m作用域中可見. 一個Translation unit 將會生成一個 object file, 就Objective-C 而言, 通常意味著每一個類都有一個Translation unit: 每一個 .m 文件. 如果常量沒有使用 static 修飾, 那么編譯器將會為這個常量創建一個外部的標識符. 如果其他的 Translation unit 中定義了一個相同名稱的常量, 那么編譯器將會拋出一個異常, 如下:

deplicate symbol _kAnimationDuration in:
  XXXAnimationView.o
  XXXView.o

在某些情況下, 也許你希望將你的常量暴露出來. 例如: 考慮這么一個場景, 你也許希望你的類將會發送 Notification 去通知其他的類. Notification 擁有一個字符串的名稱, 這個字符串名稱就是你希望暴露在外的常量. 這樣做意味著, 任何一個對象想注冊你這個類的通知, 它無需知道你這個通知的字符串具體是什么內容, 它只是簡單的使用你暴露出來的這個常量即可.

這樣的常量需要用在它所定義的Translation unit 以外的地方, 所以這個常量使用的是和上文中static const 不同的方式進行定義. 定義如下:

// In the header file
extern NSString *const XXXStringConstant;

// In the implementation file
NSString *const XXXStringConstant = @"aConstant";

這個常量在.h文件中進行了聲明, 并在.m文件中進行了定義. 修飾符const 的位置非常重要. 在這個例子中意味著: XXXStringConstant 是一個常量指針指向一個 NSString, 這正是我們希望看到的. 這個指針不被允許指向另外的一個 NSString 對象.

當在包含了該.h的文件中使用了該常量, extern 關鍵字就會告訴編譯器, 在Global symbol table 中有一個 XXXStringConstant 的標識符, 也就是告訴編譯器有這么一個常量存在, 這意味著在編譯器無法找到這個常量定義的時候, 我們仍然可以使用這個常量.

事實上, 標識符出現在Global symbol table 中, 意味著我們給這樣的常量命名的時候應該非常小心. 例如: 一個負責 Application 登錄操作的類, 在登錄成功后也許需要觸發一個 Notification, 這個Notification 看起來大概是這樣:

// XXXLoginManager.h
#import <Foundation/Foundation.h>
extern NSString *const XXXLoginManagerDidLoginNotification;
@interface XXXLoginManager : NSObject
- (void) login;
@end

// XXXLoginManager.m
#import "XXXLoginManager.h"
NSString *const XXXLoginManagerDidLoginNotification = @"XXXLoginManagerDidLoginNotification";
@implemetation XXXLoginManager

- (void) login {
  [self didLogin];
}

- (void) didLogin {
  // Post a notification here
}

@end

來看看這個常量 XXXLoginManagerDidLoginNotification, 使用類名作為常量的前綴, 絕大多數系統的 Framework 都是以這種方式進行命名的. 例如 UIKit定義的通知名稱常量就是使用的相同的命名規范. 比如說: UIApplicationDidEnterBackgroundNotification等.
其他類型的常量, 也可以通過同樣的方式進行定義. 如果 Animtaion duration 需要暴露在外, 那么上文中的例子, 我們可以這樣來定義

// XXXAnimationView.h
extern const NSTimeInterval XXXAnimationViewAnimationDuration;

// XXXAnimationView.m
NSTimeInterval const XXXAnimationViewAnimationDuration = 0.3f;

相比預編譯指令定義一個宏定義, 使用這種方式定義常量是一個更好的方式. 因為編譯器可以確保這個常量是不允許被修改的. 預編譯指令定義的宏定義有可能被重復定義, 意味著 Application 的不同部分使用相同的宏定義將會出現不同的值.
最后, 避免使用預編譯指令去定義常量, 取而代之, 使用staticconst 去定義常量.

Things to Remember:

  • 避免使用預編譯定義常量, 這種方式沒有任何的類型信息. 有可能在沒有任何警告的情況下出現重定義的情況, 在你的Application中產生不一致的值.
  • 使用 staticconst 關鍵字在 .m 文件中定義常量, 這個常量將不會對外暴露, 所以他們的命名是不需要命名空間的.
  • .h文件中使用 extern 關鍵字進行聲明, 并在相應的.m文件中進行定義, 這個常量將會出現在 Global symbol table 中, 所以這個常量的命名需要使用一定的命名空間, 通常我們使用類名作為這個常量的前綴.

Lemon龍說:

如果您在文章中看到了錯誤 或 誤導大家的地方, 請您幫我指出, 我會盡快更改

如果您有什么疑問或者不懂的地方, 請留言給我, 我會盡快回復您

如果您覺得本文對您有所幫助, 您的喜歡是對我最大的鼓勵

如果您有好的文章, 可以投稿給我, 讓更多的 iOS Developer 在簡書這個平臺能夠更快速的成長

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容