Item4: Prefer Typed Constants to Preprocessor #define

在工作中, 我们经常会定义常量。 例如:考虑一个场景, 当一个 UIView 的子类,使用动画进行presentdismiss. 此时也许你会想到一个非常典型的常量就是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

使用 staticconst 修饰常量非常重要. 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 的不同部分使用相同的宏定义将会出现不同的值.
最后, 避免使用预编译指令去定义常量, 取而代之, 使用staticconst 去定义常量.

Things to Remember:

  • 避免使用预编译定义常量, 这种方式没有任何的类型信息. 有可能在没有任何警告的情况下出现重定义的情况, 在你的Application中产生不一致的值.
  • 使用 staticconst 关键字在 .m 文件中定义常量, 这个常量将不会对外暴露, 所以他们的命名是不需要命名空间的.
  • .h文件中使用 extern 关键字进行声明, 并在相应的.m文件中进行定义, 这个常量将会出现在 Global symbol table 中, 所以这个常量的命名需要使用一定的命名空间, 通常我们使用类名作为这个常量的前缀.

Lemon龙说:

如果您在文章中看到了错误 或 误导大家的地方, 请您帮我指出, 我会尽快更改

如果您有什么疑问或者不懂的地方, 请留言给我, 我会尽快回复您

如果您觉得本文对您有所帮助, 您的喜欢是对我最大的鼓励

如果您有好的文章, 可以投稿给我, 让更多的 iOS Developer 在简书这个平台能够更快速的成长

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容