要点:
1.赶紧抛弃使用 #define 定义常量的陋习吧,使用 const 修饰符配合类型定义;
2.使用 C++ 11 新标准,为枚举变量指定底层数据类型。
常量定义
大概国内很多人接触编程时都是 C 或是 C++吧,我几年前考研时就是学习的 C++,大学本科有 C 语言课程,不过都没有使用这两门语言认真开发过程序。学习这两门语言时,定义常量使用 #define几乎是必考知识点。OC 是 C 的超集,在去年自学 OC 的过程中使用 #define 定义常量就是顺理成章的事了。本书不推荐这么做,因为这样定义的常量不具备类型信息,比如要设定一个动画播放时间:
#define ANIMATION_DURATION 0.3
这样写已经尽最大努力传达了常量的意义:时间,但是不如下面的更清晰明确
static const NSTimeInterval kAnimationDuration = 0.3;
出场自带常量意义, 通过类型明确了使用场景,使用 #define 是通过文本替换的方式,不那么安全,万一手残......只设定一个可能看不出多大好处,但需要大批量设定一堆常量时,后者的好处显而易见。
添加 static 后该常量仅在该实现文件里可以使用;如果不添加,则该常量会成为一个全局常量,如果其他地方也定义了同名变量,编译器就会报错。定义的常量如果仅限在内部使用,就不要在常量定义公开在头文件中;要共享给外部使用,一般在头文件中这样公布:
extern NSString * const nidechangliang;
然后在实现文件中来实现这个常量:
NSString * const nidechangliang = @"你的常量哦";
如果使用 #define,你只能在头文件里定义常量,这样一来,怎么说呢,就没那么优雅了吧。当然不止如此,看下面。
书中还提到一个好处,使用 const + 类型定义常量能让编译器确保该常量不会被更改,这也是常量的意义;但使用#define 则可能被重新定义,从而导致程序中不同部分使用的常量值不一样。刚开始看这里,不明白怎么会导致不同部分的同一份常量的值会不一样。Google 一番,发现使用 #define 定义的常量还能被重新定义,读书太少啊。使用方法如下:
#define myConstant 我的常量
上面的代码在某处定义了一个常量,然后在另外一个地方使用如下重新定义:
#ifdef myConstant
#undef myConstant
#endif//此处已解除了原先定义的常量
#define myConstant 我的常量哦//重新定义
更简洁的写法:
#undef myConstant
#define myConstant 是我的常量哦
这样一来,违背了定义常量的初衷,而且编译器不会产生警告,要是出了问题,这调试起来可真麻烦,宏定义果真黑魔法。所以赶紧抛弃 #define,拥抱 const + 类型定义吧。
总结新的方法 const修饰符 + 类型定义的好处是:
1.附带类型信息,阅读代码时清楚常量意义。
2.防止在其他地方被修改,确保常量是真正的常量,这才是相对 #define 方法的最大优势。
Note 1: const 修饰符在常量定义的位置应该处于类型定义的右边,常量定义应该从右往左解读,比如NSString * const SDEStringConstant = @"seedante";
,此处 SDEStringConstant 是一个常量,且是个指针,指向一个 NSString 对象。
Note 2: 在定义内部使用的常量时,使用 static + const。static 意味这常量仅在定义该常量的文件内可见:如果不加 static,编译器会为该变量创建一个外部符号,如果其他地方也定义了同名变量或常量,就会报错,因为冲突了;加上 static 后,编译器不会为该变量创建符号,不会产生同名冲突。
枚举定义
以往定义枚举时,所用的数据类型取决于编译器。C++11 标准修订了枚举的特性,其中一项是可以指定枚举变量的底层数据类型,最大的好处是可以向前声明枚举变量了。因为不指定底层数据类型,编译器不知道枚举变量的数据类型,也就不知道该分配多少空间了。
语法:
enum SDEXYZStatus: NSInteger {/*...*/}//指定使用 NSInteger作为枚举的数据类型
在 Foundation 框架中定义了一些辅助的宏 NS_ENUM 和 NS_OPTIONS 来使用新的特性,这些宏具备向后兼容能力。通常我们还会结合 typedef 让枚举定义的语法更简洁,但在 Xcode 里,typedef 和宏的组合并没有得到直接支持,还得手动组合。
typedef NS_ENUM(NSUInteger, SDEXYZState){
SDEXYZStateOn,
SDEXYZStateOff
}
typedef NS_OPTIONS(NSUInteger, SDEXYZDirection){
SDEXYZDirectionEast = 1<<0,
SDEXYZDirectionWest = 1<<1,
SDEXYZDirectionSouth = 1<<2,
SDEXYZDirectionNorth = 1<<3
}
枚举通常结合 switch 语句使用,书中提醒用枚举定义状态机时,最好不要有 default 分支。使用 default 分支的话,如果枚举变量增加新的状态,编译器不会有警告信息,万一你忘记在 switch 中处理这个状态就不好了。
另外推荐一篇博客:《使用状态机的好处》。
Swift 中的枚举
相对于 OC 的枚举变量类型仅仅支持整数,Swift 中的枚举强大得多。Swift 中的枚举的使用模式有两种:原始值( rawValue )与相关值( Associated Values )
1.原始值模式,类似 OC 中的枚举,不过支持多种数据类型:String, Character, Int, Float;使用 Int 时,和 OC 一样,支持后续枚举成员自动递增;枚举成员的原始值不可以相同。
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
2.相关值模式:英文含义更容易理解,可以理解为枚举成员的附带信息,支持任意类型。
enum Barcode {
case UPCA(Int, Int, Int)
case QRCode(String)
}
上面定义了一个类型代码系统,有两个枚举成员,每个成员代表了一类代码。
除此之外,Swift 里的枚举的成员变量也可以不赋值。
enum CompassPoint {
case North
case South
case East
case West
}
还支持泛型:
enum Either<A,B> {
case Left<A>
case Right<B>
}
此外,还可以这样混用:
enum Github {
case Zen
case UserProfile(String)
case Repositories(username: String, sortAscending: Bool)
}
除此之外,Swift 还支持函数。总的来说,OC 中的枚举仅仅是升级版的整数,而 Swift 里的枚举则是拥有限定数量值的混合类型。
可以看看去年 Swift 刚推出时 objc.io 的文章或翻译版本《Swift 的强大之处》 。如果说在去年 Swift 还可以先观望等待稳定,今年就应该开始学习了。