本节书摘来自华章出版社《编写高质量代码:改善Objective-C程序的61个建议》一 书中的第2章,作者:刘一道,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
建议14:有序对象适宜存于数组,而无序对象适宜存于集
虽然可以使用C语言数组来保存标值的集合,甚至是对象指针,但是在Objective-C代码中,大多数集合是Cocoa和Cocoa Touch集合类中的某一个类的实例,如NSArray、 NSSet 和 NSDictionary。
使用这些类可用来管理对象组,这意味着任何添加到集合的项必须是Objective-C类的一个实例。如果需要添加一个标量值,就必须首先创建一个合适的NSNumber或NSValue的实例来表示它。
某种程度上维护着每个集合对象的单独的副本,集合类使用强引用来跟踪它们的内容。这意味着添加到集合中的任何对象,其生命周期至少要跟集合的生命周期一样长,正如“通过所有权和责任来管理的对象图”。除了跟踪它们的内容之外,Cocoa和Cocoa Touch的每个集合类还可以很容易地执行某些任务,例如枚举,访问特定项或找出特定的对象是否是集合的一部分。
虽然NSArray、NSSet和NSDictionary类都是不可改变的,这意味着他们的内容在创建时将要进行设置;但它们每一个都有可变的子类,利用他们的子类,可以随意添加或删除对象。
下面对于集合中的数组和集,适宜存储对象的差异性分别进行介绍。
- 有序对象适合存于数组
NSArray 用来表示对象有序的集合。唯一的要求是,每一项必须是Objective-C对象,也就是说,没有要求每个对象必须是同一类的一个实例。因此,一组对象的顺序很重要时,就该使用数组。例如,许多应用程序使用数组向表格视图中的行或菜单中的项目提供内容;索引为 0 的对象对应于第一行,索引为1的对象对应于第二行,依此类推。访问数组中对象的时间,比访问集合中对象的时间长,如图2-7所示。
数组以有序序列储存对象。
1)创建数组
NSArray 类提供许多初始化程序和类工厂方法,用于创建数组和对数组进行初始化,但有几个方法尤其常见和实用。可以使用 arrayWithObjects:count: 和 arrayWithObjects: 方法(及其对应的初始化程序 initWithObjects:count: 和 initWithObjects:),从一系列对象创建数组。使用前一种方法时,第二个参数指定第一个参数(静态 C 数组)中的对象数;使用后一种方法时,其参数为以逗号分隔的对象序列(以 nil 终止)。
// Compose a static array of string objects
NSString *objs[3] = {@"One", @"Two", @"Three"};
// Create an array object with the static array
NSArray *arrayOne = [NSArray arrayWithObjects:objs count:3];
// Create an array with a nil-terminated list of objects
NSArray *arrayTwo = [[NSArray alloc] initWithObjects:@"One", @"Two", @"Three",
nil];
创建可变数组时,可以使用 arrayWithCapacity:(或 initWithCapacity:)方法创建数组。容量参数将有关数组预期大小的提示提供给类,从而使数组在运行时更高效。数组甚至可以超过所指定的容量。
还可以使用容器字面常量 @[ ...] 创建数组,其中方括号之间的项目是以逗号分隔的对象。例如,要创建包含一个字符串、一个数字和一个日期的数组,可以编写如下代码:
NSArray *myArray = @[ @"Hello World", @67, [NSDate date] ];
2)访问数组中的对象
通常,调用 objectAtIndex: 方法访问数组中的对象,方法是指定该对象在该数组中的索引位置(从 0 开始)。
NSString *theString = [arrayTwo objectAtIndex:1]; // returns second object in
array
NSArray 提供其他方式来访问数组中的对象或其索引。例如,有 lastObject、first-ObjectCommonWithArray:和 indexOfObjectPassingTest:。
可以使用下标记号(而非使用 NSArray 的方法)访问数组中的对象。例如,要访问 myArray(上面已创建)中的第二个对象,可以编写如下代码:
id theObject = myArray[1];
与数组有关的另一个常见任务是,对数组中的每个对象执行某种操作—这时称为枚举的过程。通常通过枚举数组来决定一个对象或多个对象是否与某个值或条件匹配;如果有一个对象匹配,则使用该对象完成一项操作。可以采用以下三种方式之一枚举数组:快速枚举、使用块枚举或使用 NSEnumerator 对象。顾名思义,快速枚举通常比使用其他技巧访问数组中的对象要快。快速枚举是一项需要特定语法的语言功能:
for (type variable in array){ /* inspect variable, do something with it */ }
例如:
NSArray *myArray = // get array
for (NSString *cityName in myArray) {
if ([cityName isEqualToString:@"Cupertino"]) {
NSLog(@"We're near the mothership!");
break;
}
}
数个NSArray方法使用块来枚举数组,其中最简单的是 enumerateObjectsUsingBlock:。此块具有三个参数:当前对象、其索引和引用的 Boolean 值(设置为 YES 时终止枚举)。此块中的代码执行的工作,与快速枚举语句中大括号内的代码完全相同。
NSArray *myArray = // get array
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isEqual:@"Cupertino"]) {
NSLog(@"We're near the mothership!");
*stop = YES;
}
}];
3)管理可变数组
NSArray 具有其他方法用于给数组排序、搜索数组和在数组中的每个对象上调用方法。
通过调用 addObject: 方法,可将对象添加到可变数组;对象放在数组末尾。也可以使用 insertObject:atIndex:,将对象放在可变数组中的特定位置。通过调用 removeObject: 方法或 removeObjectAtIndex: 方法,可以从可变数组中移除对象。
还可以使用下标记号,将对象插入可变数组中的特定位置。
NSMutableArray *myMutableArray = [NSMutableArray arrayWithCapacity:1];
NSDate *today = [NSDate date];
myMutableArray[0] = today;
4)同一数组可以保存不同的对象
比如一个数组NSArray,这种数组里面可以保存各种不同的对象,比如这个数组里:
myArray <
0: (float) 234.33f
1: @"我是个好人"
2: (NSImage *) (俺的美图)
3: @"我真的是好人"
这是一个由4个东西组成的数组,这个数组包括一个浮点数,两个字符串和一个图片。
NSArray中不能存储基本类型,如float,int,double等,否则都会被设置为0。另外,上面这个调用必须用nil来结尾,这也意味着NSArray中不能存储nil。
- 无序对象适合储存于集
集(Sets)是类似于数组的一组对象,但按照无序组的方式来维持着不同的对象,如图2-8所示。只是其中包含的项目是无序的(而数组是有序的)。通过枚举集合中的对象,或者将过滤器或测试应用到集合,来随机访问集合中的对象(使用 anyObject 方法),而不是按索引位置或通过键访问它们。
由于集(Sets)不维持秩序,当它涉及成员资格的测试时,它们对于数组就提供了一个性能改进处理。又加上基本的 NSSet 类是不可改变的,所以在用分配和初始化类工厂方法来创建其内容时,必须是规定好的,像这样:
NSSet *simpleSet =
[NSSet setWithObjects:@"Hello, World!", @42, aValue, anObject, nil];
用NSArray,通过initWithObjects: 和 setWithObjects: 这两种方法可能会使其中一个零终止和参数的数目可变。可变的 NSSet 子类是 NSMutableSet。即使不止一次在集(Sets)中尝试添加一个对象,集也只能存储一个单独对象的一个引用,如下所示:
NSNumber *number = @42;
NSSet *numberSet =
[NSSet setWithObjects:number, number, number, number, nil];
// numberSet only contains one object
尽管集合对象在 Objective-C 编程中不如字典和数组那么常用,但它们在某些技术中是重要的集类型。在 Core Data(一种数据管理技术)中,当声明对多关系的属性时,属性类型应该是 NSSet 或 NSOrderedSet。集合对于 UIKit 框架中的原生触摸事件处理也很重要,例如:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *theTouch = [touches anyObject];
// handle the touch...
}
有序集合是集合基本定义的一个例外。在有序集合中,集合中的项目顺序很重要。有序集合中测试成员资格比数组中要快。
- 使用集合可保持对象图的持久化
使用NSArray 和 NSDictionary 类可以容易地直接将其内容写入磁盘,像这样:
NSURL *fileURL = ...
NSArray *array = @[@"first", @"second", @"third"];
BOOL success = [array writeToURL:fileURL atomically:YES];
if (!success) {
// an error occured...
}
如果每个容器性对象是属性列表类型NSArray、NSDictionary、NSString、NSData、 NSDate和NSNumber之一,则可能要从磁盘重新创建整个层次结构,像这样:
NSURL *fileURL = ...
NSArray *array = [NSArray arrayWithContentsOfURL:fileURL];
if (!array) {
// an error occurred...
}
如果需要在容器中继续存在其他类型的对象,且其是上面所示的标准属性列表类,那么就可以使用归档器对象来创建容器,如NSKeyedArchiver,来创建档案收集的对象。
创建一个归档文件,唯一的要求是每个对象必须支持 NSCoding 的协议。这意味着每个对象必须知道如何对自己进行编码到存档(通过实施 encodeWithCoder:方法)和解码本身当从一个现有的存档中读取(initWithCoder: 方法)。
NSArray、 NSSet、NSDictionary 类和其可变的子类,都支持 NSCoding,这意味着可以坚持使用归档器对象的复杂层次结构。如果使用界面生成器窗口和视图的布局,例如,生成的 nib 文件只是直观地创建了对象层次结构的存档。在运行时,nib 文件是未归档到使用相关的类的对象的层次结构。
要点
(1)数组(NSArray)可维持持续性,故适宜存储有序的对象,但每一项必须是Objective-C对象。集(Sets)不维持秩序,故适宜存储无序对象。
(2)同一数组(NSArray)可以保存不同的对象,但不能存储float、int、double等基本类型和nil,否则存储基本类型都会被设置为0,不能存储nil是因为数组必须用nil来结尾。
(3)快速枚举是访问数组(NSArray)中的对象的一种比较快的方法。
(4)使用NSArray 和 NSDictionary 类可以直接将其内容写入磁盘进行持久化。