多用類型常量,少用#define預處理指令
編寫代碼時經常要定義常量,例如,要寫一個UI視圖類,此視圖顯示出來之后就播放動畫,然后消失.你可能想把播放動畫的時間提取為常量.掌握了Objective-C與其C語言基礎的人,也許會用這種方法來做"
#define ANIMATION_DURATION 0.3
上述預處理指令會把源代碼中的 ANIMATION_DURATION
字符串替換為 0.3.這可能就是你想要的效果,不過這樣定義出來的常量沒有類型信息."持續"這個詞看上去應該與時間有關,但是代碼又未明確指出.此外,預處理過程會把碰到的所有ANIMATION_DURATION
一律替換成0.3,這樣的話假設此指令聲明在某個頭文件中,那么所有引入這個頭文件的代碼,其ANIMATION_DURATION
都會被替換.
要想解決真個問題,那么就應該設法利用編譯器的某些特性才對.有個辦法比用處理指令來定義常量更好.比方說,下面這行代碼就定義了一個類型為NSTimeInterval
的常量:
statio const NSTimeInterval kAnimationDuration = 0.3;
請注意,用此方式定義的常量包含類型信息,其好處是清楚地描述了常量的含義.由此可知該常量類型為NSTimeInterval
,這有助于為其編寫開發文檔.如果要定義許多常量,那么這這種方式能令稍后閱讀代碼的人更容易理解其意圖.
還要注意常量名稱.常用的命名法是:若常量局限于某"編譯單元",也就是"實現文件"之內,則在前面加字母k;若常量在類之外可見,則通常以類名為前綴.
定義常量的位置很重要.我們總喜歡在頭文件里聲明預處理指令,這樣做真的很糟糕,當常量名稱有可能互相沖突時更是如此,例如ANIMATION_DURATION
這個常量名就不該用在頭文件中,因為所用引入了這份頭文件的其他文件的其他的文件中都會出現這個名字.其實就連用static const
定義的那個常量也不應出現在頭文件里.其實就連用static const
定義的那個常量也不應該出現在頭文件里.因為Objective-C沒有"名稱空間"這一概念,所以那樣做等于聲明一個kAnimationDuration
的全局變量.此名稱應該加上前綴,以表明其所屬的類,例如可改為EOCViewClassAnimationDuration
.
若不打算公開某個常量,則應將其定義在使用該常量的實現文件里.比方說,要開發一個使用UIKit框架的iOS應用程序,取UIview
子類中含有表示動畫播放時間的常量,那么可以這樣寫:
#import<UIKit/UIKit.h>
@interface EOCAnimatedView:UIView
- (void)animate;
@end
#import"EOCAnimatedView.h"
static const NSTimeInterval kAnimationDuration = 0.3;
@implementation EOCAnimatedView
- (void)animate {
[UIViewanimateWithDuration:kAnimationDuration animations:^(){}];
}
@end
變量一定要同時使用static
與const
來聲明.如果視圖修改由const
修飾符所用聲明的變量,那么編譯器就會報錯.在本例中,我們正是希望這樣:因為動畫播放時長為定值,所以不應修改.而stactic
修飾符則意味著該變量僅在定音此變量的編譯單元中可見.編譯器每收到一個編譯單元,就會輸出一份"目標文件"在Objective-C的語境下,"編譯單元"一次通常指每個類的實現文件(以.m為后綴名).因此,在上述范例代碼中聲明的kAnimationDuration
變量,其作用域僅限由EOCAnimatedView.m
所生成的目標文件中.假如聲明此變量時加static
,則編譯器會為它創建一個"外部符號".此時若是另一分編譯單元中也聲明了同名變量,那么編譯器就拋出一條錯誤消息:
duplicate symbol_kAnimationDuration in:
EOCAnimatedView.o
EOCOtherView.o
實際上,如果一個變量既聲明為static
,又聲明為const
那么編譯器根本不會創建符號,而是會像#define
預處理指令一樣,把所有遇到的變量都替換為常值.不過還要記住:用這種方式定義的常量帶有類型信息.
有時候需要對外公開某個常量.比方說,你可能要在類代碼中調用NSNotificationCenter
已通知他人.用一個對象來派發通知,令其他欲接收通知的對象的向該對象注冊,這樣就能實現此功能了.派發通知時,需要使用字符串來表示此項通知的名稱,而這個就可以聲明為一個外界可見的常值變量.這樣的話,注冊者無須知道實際字符串值,只需要以常值變量來注冊自己想要接收的通知即可.
此類常量需放在"全局符號表"中以便可以在定義該常量的編譯單元之外使用,因此,其定方式與上例子演示的 static const
有所不同.應該這樣來定義:
extern NSSting *const EOCStingConstant;
NSSting *const EOCStingConstant = @"VALUE";
這個常量在頭文件中"聲明",且在實現文件中"定義".注意const
修飾符在常量類型中的位置.常量定義應從右至左解讀,所以在本例中EOCStingConstant
就是"一個常量,而這個常量的是指針,指向NSSting對象".這與需求相符:我們不希望有人改變此指針常量,使其指向另一個NSSting對象.
編譯器看到頭文件中的extern
關鍵字,就能明白如何在引入此頭文件的代碼中處理該常量了.這個關鍵字是要告訴編譯器,在全局符號表中將會有一個名叫EOCStingConstant 的符號.也就是說,編譯器無需查看其定義,即允許代碼使用此常量.因為它知道,當鏈接成二進制文件之后,肯定能找到這個常量.
此類常量必須要定義,而且只能定義一次.通常將其定義在與聲明該常量的頭文件相關的實現文件里.由實現文件生成目標文件時,編譯器會在"數據段"為字符串分配存儲空間.鏈接器會把此目標文件與其他目標文件相鏈接以生成最終的二進制文件.凡是是用到EOCStingConstant這個全局符號的地方,鏈接器都能將其解析.
因為符號要放在全局符號表里,所以命名常量時需謹慎.例如,某應用程序中有個處理登錄操作的類,在登錄完成后會發出通知.派發通知所用的代碼如下:
#import<Foundation/Foundation.h>
Extern NSSting *const EOCLoginManagerDidLoginNotification;
@interface EOCLoginManager:NSObject
- (void)login;
@end
#import "EOCLoginManger.h"
NSSting *cost EOCLoginManagerDidLoginNotification = @"EOCLoginManagerDidLoginNotification";
@implementaion EOCLoginManager
-(void)login {
}
-(void)p_didLogin {
[[NSNotificationCenter defaultCenter]postNotificationName:EOCLoginManagerDidLoginNotificationobject:nil];
}
@end
注意常量的名字.為避免名稱沖突,最好是用與之相關的類名做前綴,系統框架中一般都這樣做.例如UIKit就按照這種方式來聲明用作通知名稱的全局常量.其中有類似UIApplicationDidEnterBackgroundNotification與UIApplictaionWillEnterForegroundNotification這樣的常量名.
其他類型的常量也是如此.假如要報前例中EOCAnimationView類里的動畫播放時間長對外公布,那么可以這樣聲明:
extern const NSTimeInterval EOCAnimatedViewAnimationDuration;
const NSTimeInterval ROCAnimatedViewAnimationDuration = 0.3;
這樣定義常量要優于使用#define
預處理指令,因為編譯器會確保常量值不變.一旦在EOCAnimatedView.m
中定義好,即可隨處使用.而采用預處理指令所定義的常量可能會無意中遭人修改從而導致應用程序各個部分所使用的值互補相同.
總之,勿使用預處理指令定義常量,而應該借助編譯器來確保常量正確,比方說可以在實現文件中用static const
來聲明產量,也可以聲明一些全局常量.