多用類型常量忧饭,少用#define預(yù)處理指令

編寫代碼時經(jīng)常要定義常量。例如筷畦,要寫一個UI視圖類词裤,此視圖顯示之后要播放動畫,然后消失鳖宾。你可能想把播放動畫的時間提取為常量吼砂,掌握Objective-C與其C語言基礎(chǔ)的人,也許會選擇這種做法來做:

#define ANIMATION_DRUATION 0.3

上述預(yù)處理指令會把源代碼中的ANIMATION_DRUATION字符串替換為0.3鼎文,這可能就是你想要的效果渔肩,不過這樣定義出來的常量沒有類型信息,“持續(xù)”(druation)這個詞看上去與時間有關(guān)漂问,但是代碼中又未明確指明赖瞒。預(yù)處理這個過程會把碰到的所有ANIMATION_DRUATION一律替換成0.3女揭,這樣的話,假設(shè)此指令聲明在某個頭文件中栏饮,那么引入了這個頭文件的代碼吧兔,其ANMIATION_DRUATION都會被替換。
要想解決此問題袍嬉,需要想辦法利用編譯器的某些特性才行境蔼。有個辦法比預(yù)處理指令來定義常量更好。比方說伺通,下面這行代碼就定義了一個類型為NSTimeInterval的常量:

static const NSTimeInterval kAnimationDuration = 0.3

請注意箍土,此方式定義的常量包含類型信息,其好處是清楚描述了常量的含義罐监。由此可知該類型常量為NSTimeInterval吴藻,這有助于其編寫開發(fā)文檔御蒲。如果定義許多常量恍风,那么這種方式能令稍后閱讀代碼的人更容易理解去意圖。
還要注意常量名稱介评,常量的命名方法是:若常量局限于某“編譯單元”(translation unit矢空,也就是實(shí)現(xiàn)文件 implementation file)之內(nèi)航罗,則在之前加上字母k,若常量在類之外可見屁药,則通常以類名為前綴粥血。
定義常量的位置很重要。我們總喜歡在頭文件里面聲明預(yù)處理指令酿箭,這樣做真的很糟糕复亏,當(dāng)常量名稱有可能互相沖突時更是如此。例如ANMIATION_DURATION這個常量就不應(yīng)該用在頭文件中缭嫡,因?yàn)樗幸脒@份頭文件的其他文件中都會出現(xiàn)這個名字蜓耻。其實(shí)就連 static const 定義的那個常量都不應(yīng)該出現(xiàn)在頭文件中。因?yàn)镺bjective-C沒有“命名空間”(namespace)這一概念械巡,所以那樣做等于聲明了一個kAnimationDuration的全局變量,此名稱應(yīng)該加上前綴饶氏,以表明所屬的類讥耗,例如可改為EOCViewClassAnimationDuration。
弱不打算工改某個常量疹启,則應(yīng)將其定義在使用該常量的實(shí)現(xiàn)文件里古程。比方說,要開發(fā)一個使用UIKit框架的iOS應(yīng)用程序喊崖,其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來聲明雇逞。如果試圖修改有static修飾符所聲明的變量,編譯器會報錯茁裙。在本例中塘砸,我們正是希望這樣:因?yàn)閯赢嫴シ艜r長為定值,所以不應(yīng)修改晤锥。而static修飾符意味著次變量僅在定義此變量的編譯單元中可見掉蔬。編譯器每收到一個編譯單元,就是輸出一份“目標(biāo)文件”(object file)矾瘾。在Objective-C語境下女轿,“編譯單元”(translation unit)一詞通常指每個類的實(shí)現(xiàn)文件(以.m為后綴名)。因此壕翩,在上述示例代碼中聲明的kAnimationDuration變量,其作用域僅局限于EOCAnimatedView.m所生成的目標(biāo)文件中放妈。假如聲明此變量是不加static北救,則編譯器會為他創(chuàng)建一個“外部符號”(external symbol),此時大猛,若另一個編譯單元也聲明了同名變量扭倾,那么編譯器就會拋出這樣一個錯誤:

duplicate symbol _kAnimationDuration in:
    /Users/suchao/Library/Developer/Xcode/DerivedData/textAAA-gmmodsxqupjwwparrkowvdkbobfc/Build/Intermediates.noindex/textAAA.build/Debug-iphonesimulator/textAAA.build/Objects-normal/x86_64/SecondViewController.o
    /Users/suchao/Library/Developer/Xcode/DerivedData/textAAA-gmmodsxqupjwwparrkowvdkbobfc/Build/Intermediates.noindex/textAAA.build/Debug-iphonesimulator/textAAA.build/Objects-normal/x86_64/FirstViewController.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

實(shí)際上,如果一個變量既聲明為static挽绩,有聲明為const膛壹,那么編譯器根本不會創(chuàng)建符號,而是想#define預(yù)處理指令一樣唉堪,把所有遇到的變量都替換為常值模聋。不過還是要記住,用這種方式定義的常量含有類型信息唠亚。
有時需要對外公開某個常量链方。比方說,你可能要在類代碼中調(diào)用NSNotificationCenter通知其他人灶搜。用一個對象來派發(fā)通知祟蚀,令其他與接收的對象向該對象注冊,這樣就能實(shí)現(xiàn)此功能了割卖。派發(fā)通知時前酿,需要使用字符串來表示此項(xiàng)通知的名稱,而這個名字就可以聲明為一個外部可見的一個“常值變量”(constant variable)鹏溯。這樣的話罢维,注冊這無需知道實(shí)際字符串值,只需以常值變量來注冊自己想要接收的通知即可丙挽。
此類變量需要放在“全局符號表中”(global symbol table)肺孵,以便可以在定義該常量的編譯單元之外使用匀借。因此,其定義方式與上例演示的static const有所不同平窘。應(yīng)該這樣來定義:

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

// In the Implementation file
NSString *const EOCStringConstant = @"VALUE";

這個常量在頭文件中“聲明”吓肋,且在實(shí)現(xiàn)文件中“定義”。注意const修飾符在常量類型中的位置初婆。常量定義應(yīng)從右至左解讀蓬坡,所以在本例中,EOCStringConstant就是“一個常量磅叛,而且這個常量是指針屑咳,指向NSString對象”。這與需求相符弊琴,我們不希望有人改變此指針常量兆龙,使其指向另一個NSString對象。
編譯器看到頭文件中extern關(guān)鍵字敲董,就能明白如何在引入此頭文件的代碼中處理該常量了紫皇。這個關(guān)鍵字就是要告訴編譯器,在全局符號表中將會有一個名叫EOCStringConstant符號腋寨。也就是說聪铺,編譯器無需查看其定義,即允許代碼使用此常量萄窜。因此他知道铃剔,在鏈接成二進(jìn)制文件之后,肯定能找到該常量查刻。
此常量必須要定義键兜,而且只能定義一次。通常將其定義在與聲明該常量頭文件相關(guān)的實(shí)現(xiàn)文件里穗泵,由實(shí)現(xiàn)文件生成目標(biāo)文件時普气,編譯器會在“數(shù)據(jù)段”(data section)為字符串分配內(nèi)存空間。鏈接器會把此目標(biāo)文件與其他目標(biāo)文件相連接佃延,生成最終的二進(jìn)制文件现诀。凡是用到EOCStringConstant這個全局符號的地方,鏈接器都能將其解析履肃。
因?yàn)榉栆诺饺址柋砝锔峡悦A繒r需要謹(jǐn)慎。例如榆浓,某個應(yīng)用程序中有個處理登錄操作的類,在登錄完成之后會發(fā)出通知撕攒。派發(fā)通知的代碼如下:

// EOCLoginManager.h
#import "Foundation/Foundation.h"
extern NSString *const EOCLoginManagerDidLoginNotification;

@interface EOCLoginManager : NSObject
- (void)login;
@end

// EOCLoginManager.m
#import "EOCLoginManager.h"
NSString *const EOCLoginManagerDidLoginNotification = @"EOCLoginManagerDidLoginNotification";

#implementation EOCLoginManager
- (void)login{
    //perform login asynchronously, then call "p_didLogin"
}

- (void) p_didLogin{
    [[NSNotification defaultCenter] postNotificationName: EOCLoginManagerDidLoginNotification object:nil];
}
@end

注意常量的名字陡鹃。為避免命名沖突烘浦,最好使用與之相關(guān)的類名做前綴。系統(tǒng)框架一般都是這樣做的萍鲸,例如UIKit就按照這種方式來聲明用作通知名稱的全局變量闷叉。其中有類似UIApplicationDidEnterBackgroundNotification與UIApplicationWillEnterForegroundNotification這樣的常量名。
其他類型常量也是如此脊阴。假如要把前例中的EOCAnimatedView類里的動畫時長對外公布握侧,那么可以這樣聲明:

// EOCAnimatedView.h
extern const NSTimeInterval EOCAnimatedViewAnimationDuration;
//EOCAnimatedView.m
const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3;

這樣的定義常量優(yōu)于#define預(yù)處理指令,因?yàn)榫幾g器會確保常量值不變嘿期,一旦在EOCAnimate的View中定義好品擎,可隨處使用。而采用#define預(yù)處理的指令所定義的常量可能會無意遭人修改备徐,從而導(dǎo)致應(yīng)用程序各個部分所使用的值互不相同萄传。
總之,勿使用預(yù)處理指令定義常量蜜猾,而應(yīng)借助編譯器來確保常量值正確秀菱,比方說可以再實(shí)現(xiàn)文件中用static const來聲明常量。也可以聲明一些全局常量蹭睡。

要點(diǎn):

不要用預(yù)處理指令來定義常量衍菱,這樣定義出來的常量不含類型信息。編譯器只會在編譯前據(jù)執(zhí)行查找與替換操作肩豁。及時有人重新定義了常量值脊串,編譯器也不會產(chǎn)生警告信息,這導(dǎo)致應(yīng)用程序中的常量值不一致蓖救。
在實(shí)現(xiàn)文件中使用 static const 來定義“只在編譯單元內(nèi)可見的常量”(translation-unit-spacifIc constant)洪规。由此常量不會再全局符號表中。所以無需為其名稱加前綴循捺。
在頭文件用extern來聲明全局常量斩例,并在相關(guān)實(shí)現(xiàn)文件中定義其值。這種常量會出現(xiàn)在全局符號表中从橘。所以其名稱應(yīng)加以區(qū)隔念赶,通常用與之相關(guān)的類名做前綴。

此文章是讀《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法》學(xué)習(xí)筆記:

第四條:多用類型常量恰力,少用#define預(yù)處理指令

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叉谜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子踩萎,更是在濱河造成了極大的恐慌停局,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異董栽,居然都是意外死亡码倦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門锭碳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來袁稽,“玉大人,你說我怎么就攤上這事擒抛⊥破” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵歧沪,是天一觀的道長歹撒。 經(jīng)常有香客問我,道長槽畔,這世上最難降的妖魔是什么栈妆? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮厢钧,結(jié)果婚禮上鳞尔,老公的妹妹穿的比我還像新娘。我一直安慰自己早直,他們只是感情好寥假,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著霞扬,像睡著了一般糕韧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喻圃,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天萤彩,我揣著相機(jī)與錄音,去河邊找鬼斧拍。 笑死雀扶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肆汹。 我是一名探鬼主播愚墓,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼昂勉!你這毒婦竟也來了浪册?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤岗照,失蹤者是張志新(化名)和其女友劉穎村象,沒想到半個月后笆环,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煞肾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年咧织,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片籍救。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖渠抹,靈堂內(nèi)的尸體忽然破棺而出蝙昙,到底是詐尸還是另有隱情,我是刑警寧澤梧却,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布奇颠,位于F島的核電站,受9級特大地震影響放航,放射性物質(zhì)發(fā)生泄漏烈拒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一广鳍、第九天 我趴在偏房一處隱蔽的房頂上張望荆几。 院中可真熱鬧,春花似錦赊时、人聲如沸吨铸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诞吱。三九已至,卻和暖如春竭缝,著一層夾襖步出監(jiān)牢的瞬間房维,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工抬纸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咙俩,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓松却,卻偏偏與公主長得像暴浦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子晓锻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容