iOS#define和static const

定義常量最好用 static const ,不用#define

編寫代碼時經常要定義常量。例如,要寫一個UI視圖類,此視圖顯示出來之後就播放動畫,然後消失。你可能想把播放動畫的時間提取爲常量。掌握了Objective-C與其C語言基礎的人,也許會用這種方法來做:

#define ANIMATION_DURATION 0.3

上述預處理指令會把源代碼中的ANIMATION_DURATION字符串替換爲0.3。這可能就是你想要的效果,不過這樣定義出來的常量沒有類型信息。“持續”(duration)這個詞看上去應該與時間有關,但是代碼中又未明確指出。此外,預處理過程會把碰到的所有ANIMATION_DURATION一律替換成0.3,這樣的話,假設此指令聲明在某個頭文件中,那麼所有引入了這個頭文件的代碼,其ANIMATION_DURATION都會被替換。

要想解決此問題,應該設法利用編譯器的某些特性纔對。有個辦法比用預處理指令來定義常量更好。比方說,下面這行代碼就定義了一個類型爲NSTimeInterval的常量:

static const NSTimeInterval kAnimationDuration = 0.3;

請注意,用此方式定義的常量包含類型信息,其好處是清楚地描述了常量的含義。由此可知該常量類型爲NSTimeInterval,這有助於爲其編寫開發文檔。如果要定義許多常量,那麼這種方式能令稍後閱讀代碼的人更易理解其意圖。

還要注意常量名稱。常用的命名法是:若常量侷限於某“編譯單元”(translation unit,也就是“實現文件”,implementation file)之內,則在前面加字母k;若常量在類之外可見,則通常以類名爲前綴。第19條詳解了命名習慣(naming convention)。

定義常量的位置很重要。我們總喜歡在頭文件裏聲明預處理指令,這樣做真的很糟糕,當常量名稱有可能互相沖突時更是如此。例如,ANIMATION_DURATION這個常量名就不該用在頭文件中,因爲所有引入了這份頭文件的其他文件中都會出現這個名字。其實就連用static const定義的那個常量也不應出現在頭文件裏。因爲Objective-C沒有“名稱空間”(namespace)這一概念,所以那樣做等於聲明瞭一個名叫kAnimationDuration的全局變量。此名稱應該加上前綴,以表明其所屬的類,例如可改爲EOCViewClassAnimationDuration。本書第19條中深入講解了一套清晰的命名方案。

若不打算公開某個常量,則應將其定義在使用該常量的實現文件裏。比方說,要開發一個使用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 {      
    [UIViewanimateWithDuration:kAnimationDuration animations:^(){         
        // Perform animations                      
    }];  
}  
    
@end

變量一定要同時用static與const來聲明。如果試圖修改由const修飾符所聲明的變量,那麼編譯器就會報錯。在本例中,我們正是希望這樣:因爲動畫播放時長爲定值,所以不應修改。而static修飾符則意味着該變量僅在定義此變量的編譯單元中可見。編譯器每收到一個編譯單元,就會輸出一份“目標文件”(object file)。在Objective-C的語境下,“編譯單元”一詞通常指每個類的實現文件(以.m爲後綴名)。因此,在上述範例代碼中聲明的kAnimationDuration變量,其作用域僅限於由EOCAnimatedView.m所生成的目標文件中。假如聲明此變量時不加static,則編譯器會爲它創建一個“外部符號”(external symbol)。此時若是另一個編譯單元中也聲明瞭同名變量,那麼編譯器就拋出一條錯誤消息:

duplicate symbol _kAnimationDuration in: EOCAnimatedView.o EOCOtherView.o

實際上,如果一個變量既聲明爲static,又聲明爲const,那麼編譯器根本不會創建符號,而是會像#define預處理指令一樣,把所有遇到的變量都替換爲常值。不過還是要記住:用這種方式定義的常量帶有類型信息。

有時候需要對外公開某個常量。比方說,你可能要在類代碼中調用NSNotificationCenter以通知他人。用一個對象來派發通知,令其他欲接收通知的對象向該對象註冊,這樣就能實現此功能了。派發通知時,需要使用字符串來表示此項通知的名稱,而這個名字就可以聲明爲一個外界可見的常值變量(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這個全局符號的地方,鏈接器都能將其解析。

因爲符號要放在全局符號表裏,所以命名常量時需謹慎。例如,某應用程序中有個處理登錄操作的類,在登錄完成後會發出通知。派發通知所用的代碼如下:
 

// 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 {      
  [[NSNotificationCenter defaultCenter] postNotificationName:EOCLoginManag    erDidLoginNotification object:nil];  
}  
@end

注意常量的名字。爲避免名稱衝突,最好是用與之相關的類名做前綴。系統框架中一般都這樣做。例如UIKit就按照這種方式來聲明用作通知名稱的全局常量。其中有類似UIApplicationDidEnterBackgroundNotification與UIApplicationWillEnterForegroundNotification這樣的常量名。

其他類型的常量也是如此。假如要把前例中EOCAnimatedView類裏的動畫播放時長對外公佈,那麼可以這樣聲明:
 

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

這樣定義常量要優於使用#define預處理指令,因爲編譯器會確保常量值不變。一旦在EOCAnimatedView.m中定義好,即可隨處使用。而採用預處理指令所定義的常量可能會無意中遭人修改,從而導致應用程序各個部分所使用的值互不相同。

總之,勿使用預處理指令定義常量,而應該藉助編譯器來確保常量正確,比方說可以在實現文件中用static const來聲明常量,也可以聲明一些全局常量。

要點

不要用預處理指令定義常量。這樣定義出來的常量不含類型信息,編譯器只是會在編譯前據此執行查找與替換操作。即使有人重新定義了常量值,編譯器也不會產生警告信息,這將導致應用程序中的常量值不一致。

在實現文件中使用static const來定義“只在編譯單元內可見的常量”(translation-unit-specific constant)。由於此類常量不在全局符號表中,所以無須爲其名稱加前綴。

在頭文件中使用extern來聲明全局常量,並在相關實現文件中定義其值。這種常量要出現在全局符號表中,所以其名稱應加以區隔,通常用與之相關的類名做前綴。


參考:http://book.51cto.com/art/201403/432132.htm


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章