在工作中, 我們經常會定義常量。 例如:考慮一個場景, 當一個 UIView 的子類,使用動畫進行present
和 dismiss
. 此時也許你會想到一個非常典型的常量就是Animation Duratation
. 也許你會對Animation Duration
有如下的定義:
#define ANIMATION_DURATION 0.3
這是一個預編譯指令: 無論何時,在你的源碼中找到了ANIMATION_DURATION
字符串, 都會替換成為 0.3
. 這看起來好像正是我們想要的效果, 但是這種定義方法沒有類型信息. 看到duration
給人的感覺好像是定義了一個和時間相關的宏, 但是并不明確. 而且預編譯指令將會盲目的替換所有出現的 ANIMATION_DURATION
字符串, 所以如果這個宏定義定義在了.h
文件中, 那么所有包含這個.h
的文件中的ANIMAATION_DURATION
字符串, 都會被替換成為0.3
.
為了解決這個問題, 這里有一個比預編譯指令更好的方法來定義一個常量, 例如下面定義的這個NSTimeInterval
常量
static const NSTimeInterval kAnimationDuration = 0.3;
記住這種格式. 它包含了類型信息, 并且很清晰的定義了這個常量是什么. 數據類型為 NSTimeInterval
, 可以很好的幫助你理解這個變量的用處. 如果你又好多定義的常量, 這種方法也會幫助你和你的小伙伴在將來更好的閱讀和理解代碼.
并且你還需要記住這個變量的是如何命名的, 通常我們約定常量都是以小寫字母k
開頭, 并且定義在 .m
文件中. 如果我們定義的這個常量需要暴露在 .h
文件中, 那么我們的常量需要以類名開頭. 在 Item 19將會更詳細的介紹命名規則.
聲明一個常量的位置是非常重要的, 有些時候, 我們會直接聲明一個 #define
預編譯指令在.h
文件中, 這是一個非常不好的習慣. 例如: 在.h
中的ANIMATION_DURATION
常量就是一個非常不好的命名方式, 它將會出現在所有包含了該.h
的文件中. 盡管使用static const
修飾的kAnimationDuration
常量的命名相對標準, 但是仍然不應該出現在.h
文件中, 因為Objective-C
沒有命名空間, 所以將會定義一個命名為kAnimationDuration
的全局變量. 命名應該加一些前綴, 來局限這個變量是在哪個類中使用, 例如:XXXViewAnimationDuration
. Item19將會解釋更多的策略使得命名更加的清晰.
一個常量如果不需要暴露在外, 那么就應該定義在它所在的.m
文件中. 例如: 如果Animation Duration
常量被用在了一個 UIView
的子類中, 應該像如下這樣定義:
// XXXAnimationView.h
#import <UIKit/UIKit.h>
@interface XXXAnimationView : UIView
- (void) animate;
@end
// XXXAnimationView.m
#import "XXXAnimationView.h"
static const NSTimeInterval kAnimationDuration = 0.3f;
@implementation XXXAnimationView
- (void) animate {
[UIView animateWithDuration: kAnimationDuration
animation:^(){
// Code Here
}];
}
@end
使用 static
和 const
修飾常量非常重要. const
修飾符意味著如果我們嘗試去修改這個常量的值, 編譯器將會向我們拋出一個錯誤, 因為常量的值是不允許被修改的. 所以對于 常量
而言, const
修飾符是必不可少的. static
修飾符意味著這個常量僅在定義的.m
作用域中可見. 一個Translation unit
將會生成一個 object file
, 就Objective-C
而言, 通常意味著每一個類都有一個Translation unit
: 每一個 .m
文件. 如果常量沒有使用 static
修飾, 那么編譯器將會為這個常量創建一個外部的標識符. 如果其他的 Translation unit
中定義了一個相同名稱的常量, 那么編譯器將會拋出一個異常, 如下:
deplicate symbol _kAnimationDuration in:
XXXAnimationView.o
XXXView.o
在某些情況下, 也許你希望將你的常量暴露出來. 例如: 考慮這么一個場景, 你也許希望你的類將會發送 Notification
去通知其他的類. Notification
擁有一個字符串的名稱, 這個字符串名稱就是你希望暴露在外的常量. 這樣做意味著, 任何一個對象想注冊你這個類的通知, 它無需知道你這個通知的字符串具體是什么內容, 它只是簡單的使用你暴露出來的這個常量即可.
這樣的常量需要用在它所定義的Translation unit
以外的地方, 所以這個常量使用的是和上文中static const
不同的方式進行定義. 定義如下:
// In the header file
extern NSString *const XXXStringConstant;
// In the implementation file
NSString *const XXXStringConstant = @"aConstant";
這個常量在.h
文件中進行了聲明, 并在.m
文件中進行了定義. 修飾符const
的位置非常重要. 在這個例子中意味著: XXXStringConstant
是一個常量指針指向一個 NSString
, 這正是我們希望看到的. 這個指針不被允許指向另外的一個 NSString
對象.
當在包含了該.h
的文件中使用了該常量, extern
關鍵字就會告訴編譯器, 在Global symbol table
中有一個 XXXStringConstant
的標識符, 也就是告訴編譯器有這么一個常量存在, 這意味著在編譯器無法找到這個常量定義的時候, 我們仍然可以使用這個常量.
事實上, 標識符出現在Global symbol table
中, 意味著我們給這樣的常量命名的時候應該非常小心. 例如: 一個負責 Application
登錄操作的類, 在登錄成功后也許需要觸發一個 Notification
, 這個Notification
看起來大概是這樣:
// XXXLoginManager.h
#import <Foundation/Foundation.h>
extern NSString *const XXXLoginManagerDidLoginNotification;
@interface XXXLoginManager : NSObject
- (void) login;
@end
// XXXLoginManager.m
#import "XXXLoginManager.h"
NSString *const XXXLoginManagerDidLoginNotification = @"XXXLoginManagerDidLoginNotification";
@implemetation XXXLoginManager
- (void) login {
[self didLogin];
}
- (void) didLogin {
// Post a notification here
}
@end
來看看這個常量 XXXLoginManagerDidLoginNotification
, 使用類名作為常量的前綴, 絕大多數系統的 Framework
都是以這種方式進行命名的. 例如 UIKit
定義的通知名稱常量就是使用的相同的命名規范. 比如說: UIApplicationDidEnterBackgroundNotification
等.
其他類型的常量, 也可以通過同樣的方式進行定義. 如果 Animtaion duration
需要暴露在外, 那么上文中的例子, 我們可以這樣來定義
// XXXAnimationView.h
extern const NSTimeInterval XXXAnimationViewAnimationDuration;
// XXXAnimationView.m
NSTimeInterval const XXXAnimationViewAnimationDuration = 0.3f;
相比預編譯指令定義一個宏定義, 使用這種方式定義常量是一個更好的方式. 因為編譯器可以確保這個常量是不允許被修改的. 預編譯指令定義的宏定義有可能被重復定義, 意味著 Application
的不同部分使用相同的宏定義將會出現不同的值.
最后, 避免使用預編譯指令去定義常量, 取而代之, 使用static
和 const
去定義常量.
Things to Remember:
- 避免使用預編譯定義常量, 這種方式沒有任何的類型信息. 有可能在沒有任何警告的情況下出現重定義的情況, 在你的
Application
中產生不一致的值. - 使用
static
和const
關鍵字在.m
文件中定義常量, 這個常量將不會對外暴露, 所以他們的命名是不需要命名空間的. - 在
.h
文件中使用extern
關鍵字進行聲明, 并在相應的.m
文件中進行定義, 這個常量將會出現在Global symbol table
中, 所以這個常量的命名需要使用一定的命名空間, 通常我們使用類名作為這個常量的前綴.
Lemon龍說:
如果您在文章中看到了錯誤 或 誤導大家的地方, 請您幫我指出, 我會盡快更改
如果您有什么疑問或者不懂的地方, 請留言給我, 我會盡快回復您
如果您覺得本文對您有所幫助, 您的喜歡是對我最大的鼓勵
如果您有好的文章, 可以投稿給我, 讓更多的 iOS Developer 在簡書這個平臺能夠更快速的成長