編寫代碼時經常要定義變量。例如:
#define ANIMATION_DURATION 0.3
上述預處理指令會把源代碼中的ANIMATION_DURATION字符串替換為0.3竖螃。這可能就是你想要的效果,不過這樣定義出來的常量沒有類型信息逗余。"持續(xù)"(duration)這個詞看上去應該與時間有關特咆,但是代碼中又未明確指出。此外录粱,預處理過程會把碰到的所有ANIMATION_DURATION一律替換成0.3腻格,這樣的話,假設此指令聲明在某個頭文件中啥繁,那么所有引入了這個頭文件的代碼菜职,其ANIMATION_DRRATION都會被替換。
要想解決此問題旗闽,應該設法利用編譯器的某些特性才對酬核。有個辦法比用預處理指令來定義常量更好。比方說适室,下面這行代碼就定義了一個類型為NSTimeInterval的常量:
static const NSTimeInterval kAnimationDuration = 0.3;
請注意嫡意,用此方式定義的常量包含類型信息,其好處是清楚地描述了常量的含義捣辆。由此可知該常量類型為NSTimeInterval蔬螟,這有助于為其編寫開發(fā)文檔。如果要定義許多常量罪帖,那么這種方式能令稍后閱讀代碼的人更易理解其意圖促煮。
還要注意常量名稱。常用的命名法是:若常量局限于某"編譯單元"(translation unit整袁,也就是"實現文件"菠齿,implementation file)之內,則在前面加字母k坐昙;若常量在類之外可見绳匀,則通常以類名為前綴。
定義常量的位置很重要炸客。我們總喜歡在頭文件里聲明預處理命令疾棵,這樣做真的很糟糕,當常量名稱有可能互相沖突時更是如此痹仙。例如是尔,ANIMATION_DURATION這個常量名就不該用在頭文件中,因為所有引入了這份頭文件的其他文件中都會出現這個名字开仰。其實就連用static const定義的那個常量也不該出現在頭文件里拟枚。因為Objective-C沒有"命名空間"(namespace)這一概念薪铜,所以那樣做等于聲明了一個名叫kAnimationDuration的全局變量。此名稱應該加上前綴恩溅,以表明其所屬的類隔箍,例如可改為EOCViewClassAnimationDuration。
若不打算公開某個常量脚乡,則應將其定義在使用該常量的實現文件里蜒滩。比方說,要開發(fā)一個使用UIKit框架的iOS應用程序奶稠,其UIView子類中含有表示動畫播放時間的常量俯艰,那么可以這樣寫:
//EOCAnimatedView.h
#import <UIKit/UIKit.h>
@interface EOCAnimatedView : UIView
- (void)animate;
@end
//EOCAnimatedView.m
#import "EOCAnimatedView.h"
static const NSTimeInterval kAnimationDuration = 0.3;
@implementation EOCAnimatedView
- (void)animate {
[UIView animateWithDuration:kAnimationDuration
animations:^(){
//Perform animations
}];
}
@end
變量一定要同時用static與const來聲明。如果試圖修改由const修飾符所聲明的變量锌订,那么編譯器就會報錯蟆炊。在本例中,我們正是希望這樣:因為動畫播放時長為定值瀑志,所以不應修改。而static修飾符則意味著該變量僅在定義此變量的編譯單元中可見污秆。編譯器每收到一個編譯單元劈猪,就會輸出一份"目標文件"(object file)。在Objective-C的語境下良拼,"編譯單元"一詞通常指每個類的實現文件(以.m為后綴名)战得。因此,在上述范例代碼中聲明的kAnimationDuration變量庸推,其作用域僅限于由EOCAnimatedView.m所生成的目標文件中常侦。假如聲明此變量時不加static,則編譯器會為它創(chuàng)建一個"外部符號"(external symbol)贬媒。此時聋亡,若是另一個編譯單元中也聲明了同名變量,那么編譯器就拋出一條錯誤消息:
duplicate symbol _kAnimationDuration in:
EOCAnimatedView.o
EOCOtherView.o
實際上际乘,如果一個變量既聲明為static坡倔,又聲明為const,那么編譯器根本不會創(chuàng)建符號脖含,而是會像#define預處理指令一樣罪塔,把所有遇到的變量都替換為常值。不過還是要記住:用這種方式定義的常量帶有類型信息养葵。
有時候需要對外公開某個常量征堪。比方說,你可能要在類代碼中調用NSNotificationCenter以通知他人关拒。用一個對象來派發(fā)通知佃蚜,令其他欲接收通知的對象向該對象注冊庸娱,這樣就能實現此功能了。派發(fā)通知時爽锥,需要使用字符串來表示此項通知的名稱涌韩,而這個名字就可以聲明為一個外界可見的常值變量(constant variable)。這樣的話氯夷,注冊者無須知道實際字符串值臣樱,只需以常值變量來注冊自己想要接收的通知即可。
此類常量需放在"全局符號表"(global symbol table)中腮考,以便可以在定義該常量的編譯單元之外使用雇毫。因此,其定義方式與上例演示的static const有所不同踩蔚。應該這樣來定義:
//In the header file
extern NSString *const EOCStringConstant;
//In the implementation file
NSString *const EOCStringConstant = @"VALUE";
這個常量在頭文件中"聲明"棚放,且實現文件中"定義"。注意const修飾符在常量類型中的位置馅闽。常量定義應從右至左解讀飘蚯,所以在本例中,EOCStringConstant就是"一個常量福也,而這個常量是指針局骤,指向NSString對象"。這與需求相符:我們不希望有人改變此指針常量暴凑,使其指向另一個NSString對象峦甩。
編譯器看到頭文件中的extern關鍵字,就能明白如何在引入此頭文件的代碼中處理該常量了现喳。這個關鍵字是要告訴編譯器凯傲,在全局符號表中將會有一個名叫EOCStringConstant的符號嗦篱。也就是說灸促,編譯器無須查看其定義腿宰,即允許代碼使用此常量甩挫。因為它知道英遭,當鏈接成二進制文件之后挖诸,肯定能找到這個常量多律。
此類常量必須要定義帮碰,而且只能定義一次殉挽。通常將其定義在與聲明該常量的頭文件相關的實現文件里昔搂。由實現文件生成目標文件時贤斜,編譯器會在"數據段"(data section)為字符串分配存儲空間猴抹。鏈接器會把此目標文件與其他目標文件相鏈接阳堕,以生成最終的二進制文件恬总。凡是用到EOCStringConstant這個全局符號的地方拭卿,鏈接器都能將其解析峻厚。
因為符號要放在全局符號表里浦夷,所以命名常量時需謹慎。
總之摹恰,勿使用預處理指令定義常量姑宽,而應該借助編譯器來確保常量正確闺阱,比方說可以在實現文件中用static const來聲明常量炮车,也可以聲明一些全局常量。