編寫代碼時經(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ù)處理指令