在工作中, 我们经常会定义常量。 例如:考虑一个场景, 当一个 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 在简书这个平台能够更快速的成长