本节书摘来自华章出版社《编写高质量代码:改善Objective-C程序的61个建议》一 书中的第2章,作者:刘一道,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
建议13:在访问集合时要优先考虑使用快速枚举
在Objective-C语言中,集合是最常用的数据类型。而对于集合的访问,要优先考虑使用快速枚举。使用快速枚举,要尽可能使用枚举新的写法。
- 尽可能使用枚举新的写法
使用Objective-C新的枚举写法,编写更简洁的代码,同时避免一些常见的陷阱。更重要的是,这些语法特性是完全向下兼容的,使用新特性编写出来的代码经过编译后形成的二进制程序可以运行在之前发布的任何OS中。
在使用枚举新的写法之前,先梳理一下最近几年来枚举类型几次功能的改进。
枚举在OS X 10.5 之前的版本中,如何在Objective-C中定义一个枚举类型呢?定义方法如下:
typedef enum {
ObjectiveC,
Java,
Ruby,
Python,
Erlang }
Language;
这种写法简单明了,用起来也不复杂,但是有一个问题,就是其枚举值的数据范围是模糊的,这个数值可能非常大,可能是负数,无法界定。
在OS X 10.5之后的版本中,就可以这样写:
enum {
ObjectiveC,
Java,
Ruby,
Python,
Erlang
};
typedef NSUInteger Language;
这种写法的好处是,首先这个枚举的数据类型是确定的,无符号整数;其次,由于采用了 NSUInteger,就可以不用考虑32位和64位的问题。但这样写所带来的问题是,数据类型和枚举常量没有显式的关联。
在XCode 4.4中,就可以这样写枚举了:
typedef enum Language : NSUInteger{
ObjectiveC,
Java,
Ruby,
Python,
Erlang
}Language;
在列出枚举内容的同时绑定了枚举数据类型NSUInteger,这样带来的好处是增强了类型检查和更好的代码可读性。
当然,对于普通开发者来说,枚举类型可能不会涉及复杂的数据,使用之前的两种写法也不会有什么大问题,但还是建议使用枚举新的写法,以增强现在写的代码在未来中有更强的适用性。
上面介绍了为何尽可能使用枚举新的写法,下面将介绍枚举在集合中的应用。
Objective-C和Cocoa 或 Cocoa Touch提供了多种方式来枚举集合的内容。虽然它可以使用传统的C 循环来遍历内容,像这样:
int count = [array count];
for (int index = 0; index < count; index++) {
id eachObject = [array objectAtIndex:index];
...
}
这时最好的做法是使用本节中描述的其他技术之一。
- 理解快速枚举
快速枚举是一种语言中用于快速安全的枚举一个集合的表达式,下面将从快速枚举的定义及其应用来分别介绍。
for…in表达式,这种快速枚举表达式是如下定义的:
for ( Type newVariable in expression ) { statements }
或者:
Type existingItem;
for ( existingItem in expression ) { statements }
这两种表达式中,expression是一个遵守NSFastEnumeration协议的对象。每次循环被迭代的对象都会返回一个对象并赋给一个循环变量,同时statements中定义的代码被执行一次。当被迭代的对象中已经没有数据可以取出时,循环变量被设为nil,如果循环在这之前被停止,那么循环变量会指向最后一次返回的值。
- 使用快速枚举可以很容易地枚举集合
许多集合类遵照了NSFastEnumeration协议,如 Foundation 中的集合类NSArray、 NSDic-tionary和 NSSet。显而易见,枚举操作可以遍历NSArray和NSSet的内容。对于其他类,相关文档会说明哪些内容会被枚举。例如,NSDictionary和NSManagedObjectModel也支持快速枚举,但是NSDictionary枚举的是它的键值,NSManagedObjectModel枚举的是它的实体。
作为一个例子,可以使用快速枚举,像这样在一个数组记录每个对象的描述:
for (id eachObject in array) {
NSLog(@"Object: %@", eachObject);
}
变量eachObject被自动设置为每个通过循环到当前对象,所以,一个日志报表显示每个对象。如果使用快速枚举字典,可以快速遍历字典键,像这样:
for (NSString *eachKey in dictionary) {
id object = dictionary[eachKey];
NSLog(@"Object: %@ for key: %@", object, eachKey);
}
快速枚举的行为很像一个标准的C 循环,这样就可以使用打破关键字来中断迭代,或继续前进到下一个元素。如果枚举有序的集合,枚举按顺序来进行。对于一个NSArray,这意味着第一次将索引为 0,第二个对象在索引1处,等等。如果需要跟踪当前索引,只需迭代计数,因为它们发生:
int index = 0;
for (id eachObject in array) {
NSLog(@"Object at index %i is: %@", index, eachObject);
index++;
}
在快速枚举集合期间,不能变异集合,即使集合是可变的。如果从循环内,尝试添加或删除集合的对象,则会生成运行时异常。
- 大多数集合也支持枚举器对象
也可以通过使用 NSEnumerator 对象枚举Cocoa和Cocoa Touch中的许多集合。例如,对于objectEnumerator 或 reverseObjectEnumerator,可以查询NSArray。快速枚举可以使用这些对象,像这样:
for (id eachObject in [array reverseObjectEnumerator]) {
...
}
在此示例中,循环将按照相反的顺序来让变数可以递回取得集合的对象,所以最后一个对象将是第一个,等等。通过反复调用枚举的nextObject的方法,它也可以遍历这些内容,像这样:
id eachObject;
while ( (eachObject = [enumerator nextObject]) ) {
NSLog(@"Current object is: %@", eachObject);
}
在这个例子中,一个while 循环用于将 eachObject 变量设置为每次循环的下一个对象。当没有更多的对象保留时,nextObject方法将返回nil,评估为false的逻辑值,以使循环停止。
当想表达相等运算符(==)时,程序员常见的错误是使用C 赋值运算符 (=) ,如果在一个条件分支或循环中设置一个变量,这样将会造成编译器发出警告,像这样:
if (someVariable = YES) {
...
}
如果真的这样做意味着重新分配一个变量(整体转让的逻辑值是左侧的最终值),可以将括号中的分配,像这样:
if ( (someVariable = YES) ) {
...
}
与快速枚举一样,在枚举进行时不可改变集合。而且,可根据名字来进行收集,使用快速枚举,相比使用手动枚举对象更快。
要点
(1)使用快速枚举,要尽可能使用枚举新的写法。
(2)和直接使用NSEnumerator相比,使用快速枚举可以更有效率,表达式更简洁。
(3)使用快速枚举,枚举更安全,因为枚举会监控枚举对象的变化,如果在枚举的过程中枚举对象发生变化会抛出一个异常。
(4)多个枚举可以同时进行,因为在循环中被循环对象是禁止修改的。另外,同其他的循环一样,可以使用break来停止循环或者使用continue来略过当次循环而进行到下一元素。