在開發(fā)中我們經(jīng)常要定義常量,比如設(shè)定一個(gè)動(dòng)畫執(zhí)行的時(shí)間,一般我們會這樣寫:
#define ANIMATION_DURATION 3.3
這條預(yù)處理命令會把源代碼中的ANIMATION_DURATION字符串替換為3.3.不過這樣做并不優(yōu)雅.原因是:
- 這樣定義出來的常量沒有類型信息(持續(xù)一詞應(yīng)該與時(shí)間有關(guān),但是未指明)
- 預(yù)處理命令會把所有的ANIMATION_DURATION全部替換成3.3(假設(shè)此命令聲明在頭文件里,那么所有引入這個(gè)頭文件的代碼,其ANIMATION_DURATION都會被替換)
有個(gè)辦法比用預(yù)處理命令來定義常量好,比如我們定義了一個(gè)類型為NSTimeInterval的常量:
static const NSTimeInterval kAnimationDuration = 3.3
這樣定義常量,好處不言而喻.這里還要注意的是常用的命名方法:若常量局限于某"編譯單元"(實(shí)現(xiàn)文件.m)內(nèi),則在前面加字母k,若是常量在類之外可見,則通常以類名為前綴.同時(shí)定義常量的位置也很重要,我們常常喜歡在頭文件里聲明預(yù)處理命令,這樣做真的是糟糕透了.特別是當(dāng)常量名稱可能相互沖突時(shí)更是令人抓狂.
比如ANIMATION_DURATION這個(gè)常量名就不該用在頭文件中,因?yàn)樗幸氪祟^文件的其他文件中都會出現(xiàn)這個(gè)名字.其實(shí)就連用static const定義的那個(gè)常量也不應(yīng)該出現(xiàn)在頭文件里.以為OC中是沒有"名稱空間"這一概念的.所有這樣做等于聲明了一個(gè)kAnimationDuration全局變量(準(zhǔn)確的講應(yīng)該加上類名CWGViewClassAnimationDuration).當(dāng)然如果不打算公開某個(gè)常量.則應(yīng)該將其定義到實(shí)現(xiàn)文件.m中.
變量一定要同時(shí)使用static和const來聲明.如果試圖修改由const修飾符聲明的變量,編譯器會報(bào)錯(cuò)的,有時(shí)這正是我們想要的結(jié)果.static修飾符意味著改變量盡在定義此變量的"編譯單元"可見.編譯器每收到一個(gè)編譯單元,就輸出一份"目標(biāo)文件",在OC中,"編譯單元"一詞通常指每個(gè)類的實(shí)現(xiàn)文件.m.因此在代碼中聲明一個(gè)kAnimationDuration變量,其作用域僅限于由CWGAnimatedView.m所生產(chǎn)的目標(biāo)文件.假如聲明時(shí)不帶static,那么編譯器就會創(chuàng)建一個(gè)外部符號,此時(shí)若另外一個(gè)編譯單元中也聲明了同名變量,那么編譯器就會拋出一條錯(cuò)誤信息:
duplicate symbol _kAnimationDuration in:
CWGAnimatedView.o
CWGOtherView.o
但是一個(gè)變量即聲明為:static,又聲明為const,那么編譯器就不會創(chuàng)建外部符號.
有時(shí)候我們需要對外公開某個(gè)常量,比方說,在類代碼中調(diào)用NSNotificationCenter以通知他人,用一個(gè)對象來派發(fā)通知,令其他要接收通知的對象向該對象注冊,這樣就能實(shí)現(xiàn)此功能了,派發(fā)通知時(shí),需要使用字符串來表示此項(xiàng)通知的名稱,而這個(gè)名字就可以聲明為一個(gè)對外可見的常值變量,這樣的話,注冊者無需知道實(shí)際字符串值,只需以常值變量來注冊自己想要接受的通知即可.
此類常量需要放在"全局符號表"中,以便可以在定義該常量的編譯單元之外使用.
// In the header file(.h)
extern NSString *const CWGViewStringConstant;
extern const NSTimeInterval CWGAnimationViewDuration;
// In the implementation file(.m)
NSString *const CWGViewStringConstant = @"VALUE";
const NSTimeInterval CWGAnimationViewDuration = 3.3;
這個(gè)常量在頭文件中"聲明",且在實(shí)現(xiàn)文件中"定義".注意const修飾符的位置,常量定義應(yīng)該從右至左解讀,所以在示例中,CWGStringConstant就是"一個(gè)常量, 而這個(gè)常量是指針, 指向NSString對象".這與需求是相符的,因?yàn)槲覀儾幌M腥烁淖兇酥羔槼A?使其指向另外一個(gè)NSString對象.
還要說一點(diǎn)就是因?yàn)榉栆旁谌址柋碇?所以一定要注意常量的命名規(guī)范.避免名稱沖突,最好使用與之相關(guān)的類名做前綴.系統(tǒng)框架中一般都是這么做的.
使用上述定義常量的方法,編譯器會確保常量值不變,而采用#define預(yù)處理指令定義的常量可能會無意中被他人修改.從而導(dǎo)致各個(gè)部分引用的值不同,引起難以排查的bug.
總結(jié):
不要使用#define預(yù)處理指令來定義常量,因?yàn)檫@樣的常量不包括類型信息,編譯器只會在編譯前執(zhí)行查找和替換操作,即使有人重新定義了常量值,編譯器也不會發(fā)出警告,這樣會導(dǎo)致應(yīng)用程序中的常量值不一致.
在實(shí)現(xiàn)文件.m中使用static const來定義"只在編譯單元張可見的常量",由于此類常量不在全局符號表中,所以無須為其名稱加前綴.
在頭文件中使用extern來聲明全局常量,并在相關(guān)文件中定義其值,這種常量要出現(xiàn)在全局符號表中,所以其名稱應(yīng)該加以區(qū)分,通常用與之相關(guān)的類名做前綴.