本章介绍如何通过 Foundation 框架使用 Objective-C 处理数组与字典。本章内容:
● 使用 NSArray 与 NSMutableArray 创建数组
● 在数组中添加、删除与插入对象
● 数组的搜索与排序
● 通过不同方式遍历数组
● 将数组的内容保存到文件系统中
● 使用 NSDictionary 与 NSMutableDictionary 创建字典
● 在字典中添加与删除对象
● 通过不同方式遍历字典
● 将字典的内容保存到文件系统中
● 使用 NSSet 与 NSMutableSet 创建集合
● 根据对象内容比较集合
● 通过不同方式遍历集合
● 在集合中添加与删除对象
注意:
在 Objective-C 中有 3 类对象集合:数组、字典与集合。到底使用哪种集合取决于应用的需要。
● 数组在列表中组织对象,列表通过整数进行索引。
● 字典通过键组织对象;字典中的每个对象都与某个键相关联,可以通过键检索对象。
● 集合包含对象,但不要假定这些对象以一定的顺序排列。集合中的对象也必须是
唯一的(不能重复)。从集合中检索对象是非常快的,因为集合没有索引带来的系统开销。因此,你会看到在需要考虑性能的场合下会用到集合。
3.1创建数组
问题
应用需要在列表中组织对象。
解决方案
Objective-C 提供了两个 Foundation 类来创建对象列表,它们分别是 NSArray 与NSMutableArray。如果列表不需要改变,那么请使用 NSArray 类;如果要向数组添加和删除对象,那么请使用 NSMutableArray 类。
说明
在 Objective-C 中,数组的创建与其他对象类似:使用 alloc 与 init 构造函数,或者使用诸如 arrayWithObjects:之类的便捷函数创建数组。如果使用 NSArray 创建数组,那么一旦数组创建完毕,就无法再进行修改。使用NSMutableArray 创建的数组可以在后面修改。
下面的示例展示了如何创建字符串数组:
NSArray *listOfLetters = [NSArray arrayWithObjects:@"A", @"B", @"C", nil];
在使用 arrayWithObjects:创建数组时,需要传递使用逗号分隔的对象列表并以 nil 结束。该例使用了 NSString对象,但对于 NSArray 与 NSMutableArray 来说,可以使用任何对象,包括从自定义类实例化得来的对象。
如果使用 NSMutableArray,那么可以通过相同的构造函数来创建数组(NSMutableArray是 NSArray 的子类)。如果后面还要向数组中添加对象,那么还可以通过 alloc 与 init 来创建 NSMutableArray 对象。表 3-1 列出了NSArray 与 NSMutableArray 类的完整构造函数列表,程序清单 3-1 列出了相关的代码。
代码
程序清单3-1 main.m
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]){
@autoreleasepool {
NSArray *listOfLetters1 = [NSArray arrayWithObjects:@"A", @"B", @"C", nil];
NSLog(@"listOfLetters1 = %@", listOfLetters1);
NSNumber *number1 = [NSNumber numberWithInt:1];NSNumber *number2 = [NSNumber numberWithInt:2];NSNumber *number3 = [NSNumber numberWithInt:3];
NSMutableArray *listOfNumbers1 = [[NSMutableArray alloc]initWithObjects:number1, number2, number3, nil];
NSLog(@"listOfNumbers1 = %@", listOfNumbers1);
id list[3];
list[0] = @"D";
list[1] = @"E";
list[2] = @"F";
NSMutableArray *listOfLetters2 = [[NSMutableArray alloc]initWithObjects:list
count:3];
NSLog(@"listOfLetters2 = %@", listOfLetters2);}
return 0;}
使用
要想使用上述代码,请从 Xcode 构建并运行 Mac 应用。通过日志查看每个数组的内容。接下来的攻略将会介绍如何引用这些数组元素,这样就可以将它们的内容打印到日志或是在程序的其他地方使用它们了。
3.2 引用数组中的对象
问题
你想要获得指向数组中对象的引用以访问它们的属性或是向对象发送消息。
解决方案
可以使用 objectAtIndex:方法获取数组中位于某个整数位置的对象引用,还可以通过lastObject 函数获取数组中最后一个对象的引用。
说明
NSArray 在列表中组织对象,列表通过以 0 开始的整数进行索引。如果你想要获得数组中某个对象的引用并且知道这个对象的位置,那么可以通过 objectAtIndex:方法获得指向这个对象的引用:
NSString *stringObject1 = [listOfLetters objectAtIndex:0];
通过函数 lastObject 可以快速获得数组中最后一个对象的引用:
NSString *stringObject2 = [listOfLetters lastObject];
通常情况下,你并不知晓对象在数组中的位置。如果已经获得对象的引用,那么可以通过 indexOfObject:函数并且将对象引用作为参数来获得对象在数组中的位置:
NSUInteger position = [listOfLetters indexOfObject:@"B"];
参见程序清单 3-2。代码
程序清单3-2 main.m
import <Foundation/Foundation.h>
int main (int argc, const char * argv[]){
@autoreleasepool {
NSMutableArray *listOfLetters = [NSMutableArray
arrayWithObjects:@"A", @"B", @"C", nil];
NSString *stringObject1 = [listOfLetters objectAtIndex:0];NSLog(@"stringObject1 = %@", stringObject1);
NSString *stringObject2 = [listOfLetters lastObject];NSLog(@"stringObject2 = %@", stringObject2);
NSUInteger position = [listOfLetters indexOfObject:@"B"];NSLog(@"position = %lu", position);
}
return 0;}
使用
要想使用上述代码,请从 Xcode 构建并运行 Mac 应用。从控制台输出可以看到,对象已被成功引用:
stringObject1 = A
stringObject2 = C
position = 1
3.3 获取数组中元素的数量
问题
应用使用数组中的内容,你需要知道数组中有多少个元素以便恰当地展现内容。
解决方案
NSArray 对象提供了 count 属性,可以通过这个属性获得数组中元素的数量。说明
要想使用 count 属性,可以对任何数组对象使用点符号(listOfLetters.count)或是发送 count消息([listOfLetters count])以获得数组中元素的数量。参见程序清单 3-3。
代码
程序清单3-3 main.m
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]){
@autoreleasepool {
NSMutableArray *listOfLetters = [NSMutableArray
arrayWithObjects:@"A", @"B", @"C", nil];NSLog(@"listOfLetters has %lu elements", listOfLetters.count);
}
return 0;}
使用
要想使用上述代码,请从 Xcode 构建并运行 Mac 应用。日志消息会显示出元素的数量:
listOfLetters has 3 elements
3.4 遍历数组
问题
你拥有对象数组,想要向数组中的每个对象发送同样的消息或是访问同样的属性。
解决方案
NSArray 对象提供了 3 种内置方式来遍历对象列表。很多人都使用 for-each 循环遍历数组中的每个元素。通过这种结构,可以使用相同的代码来遍历数组中的每个元素。
还可以使用 makeObjectsPerformSelector:withObject:方法,在这种情况下,可以传递希望每个对象都执行的方法名和一个参数。
最后,还可以通过 enumerateObjectsUsingBlock:方法将代码块作为参数应用到数组中的每个对象上。该方法的作用与 for-each 循环一样,但无须为循环本身编写代码,并且可以通过参数追踪当前元素的索引。
说明
由于该攻略需要一些示例,因此创建 3 个 NSMutableString 对象并放到新的数组中:
NSMutableString *string1 = [NSMutableString stringWithString:@"A"];NSMutableString *string2 = [NSMutableString stringWithString:@"B"];NSMutableString *string3 = [NSMutableString stringWithString:@"C"];
NSArray *listOfObjects = [NSArray arrayWithObjects:string1, string2,string3, nil];
要想使用 for-each 循环遍历 NSArray 数组,需要创建循环并提供局部变量,局部变量可以引用数组中的当前对象。你还需要添加代码供列表中的每个对象执行:
for(NSMutableString *s in listOfObjects){
NSLog(@"This string in lowercase is %@", [s lowercaseString]);
}
for-each 循环遍历数组中的每个可变字符串并将消息写到日志中。lowercaseString 是NSString 类的成员函数,能以小写形式返回字符串。
无需通过 for-each 循环就可以向数组中的每个对象发送消息,方式是发送makeObjectsPerformSelector:withObject:消息。需要将@selector 关键字传递到方法中并将方法名放到圆括号中。在属性修饰 withObject:的后面传递参数值:
[listOfObjects makeObjectsPerformSelector:@selector(appendString:)withObject:@"-MORE"];
这里所做的是向数组中的每个可变字符串发送 appendString:消息以及字符串参数
@"-MORE"。现在,每个字符串最终都会被修改,其中包含了额外的字符。
可以使用块定义应用到数组中的每个对象的代码块。块是一种代码封装方式,这样一来,代码就可以被当作对象,并以参数形式传递给其他对象。借助于 NSArray 方法enumerateObjectsUsingBlock:,就可以对数组中的每个元素执行代码块:
[listOfObjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSLog(@"object(%lu)'s description is %@",idx, [obj description]);}];
这与 for-each 循环的作用一样,但你只需要使用一行代码即可。此外,块还为需要引用的对象提供了内置参数和索引以帮助你追踪当前在数组中的位置。该例通过块并使用对象的 description 函数与 index 参数,针对列表中的每个对象向日志中写入了一条消息。参见程序清单 3-4。
代码
程序清单3-4 main.m
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]){
@autoreleasepool {
NSMutableString *string1 = [NSMutableString stringWithString:@"A"];NSMutableString *string2 = [NSMutableString stringWithString:@"B"];NSMutableString *string3 = [NSMutableString stringWithString:@"C"];
NSArray *listOfObjects = [NSArray arrayWithObjects:string1, string2,string3, nil];
for(NSMutableString *s in listOfObjects){
NSLog(@"This string in lowercase is %@", [s lowercaseString]);
}
[listOfObjects makeObjectsPerformSelector:@selector(appendString:)withObject:@"-MORE"];
[listOfObjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx,BOOL *stop) {
NSLog(@"object(%lu)'s description is %@",idx, [obj description]);}];
}
return 0;}
警告:
确保数组中的对象可以响应你所发出的消息。如果向数组中的对象发送它们无法响应的消息,那么程序就会崩溃,你会收到一条错误消息— “unrecognized selector sent toinstance”。
使用
要想使用上述代码,请从 Xcode 构建并运行 Mac 应用。日志消息会显示出对之前所创建数组应用各种遍历方式后的结果:
This string in lowercase is a
This string in lowercase is b
This string in lowercase is c
object(0)'s description is A-MORE
object(1)'s description is B-MORE
object(2)'s description is C-MORE
3.5 排序数组
问题
你使用数组来组织自定义对象,希望对象按照各自属性值的顺序出现在列表中。
解决方案
为用于数组排序的每个属性创建 NSSortDescriptor 对象,将所有这些 NSSortDescriptor对象放到一个数组中,该数组将会在后面用作参数。使用 NSArray 类的 sortedArrayUsing-Descriptors:方法并将 NSSortDescriptor 对象数组作为参数传递进去,结果会返回一个数组,这个数组中的对象已根据你指定的属性排好序。
说明
本攻略会使用到 Person 对象。程序清单 3-5 展示了 Person 对象的类定义。虽然可以通过该攻略对字符串或数字对象排序,但只有当你使用 NSSortDescriptor 处理自定义对象时,才会真正感受到它的强大功能。
自定义类叫做 Person,它有 3 个属性,分别是 firstName、lastName 与 age。Person 类还有两个方法,分别是reportState 与 initWithFirstName:lastName:andAge:,后者是自定义构造函数。
首先,创建 Person 对象数组:
//Instantiate Person objects and add them all to an array:Person *p1 = [[Person alloc] initWithFirstName:@"Rebecca"
lastName:@"Smith"
andAge:33];
Person *p2 = [[Person alloc] initWithFirstName:@"Albert"lastName:@"Case"
andAge:24];
Person *p3 = [[Person alloc] initWithFirstName:@"Anton"
lastName:@"Belfey"
andAge:45];
Person *p4 = [[Person alloc] initWithFirstName:@"Tom"
lastName:@"Gun"
andAge:17];
Person *p5 = [[Person alloc] initWithFirstName:@"Cindy"lastName:@"Lou"
andAge:6];
Person *p6 = [[Person alloc] initWithFirstName:@"Yanno"
lastName:@"Dirst"
andAge:76];
NSArray *listOfObjects = [NSArray arrayWithObjects:p1, p2, p3, p4, p5, p6,
nil];
如果打印数组中的每个元素,对象就会以你将它们放到数组中时的顺序出现。如果想要根据每个人的年龄、姓与名排序数组,那么可以使用 NSSortDescriptor 对象。需要为用于排序的每个 Person 属性提供排序描述符:
//Create three sort descriptors and add to an array:
NSSortDescriptor *sd1 = [NSSortDescriptor sortDescriptorWithKey:@"age"
ascending:YES];
NSSortDescriptor *sd2 = [NSSortDescriptor sortDescriptorWithKey:@"lastName"ascending:YES];
NSSortDescriptor *sd3 = [NSSortDescriptor sortDescriptorWithKey:@"firstName"ascending:YES];
NSArray *sdArray1 = [NSArray arrayWithObjects:sd1, sd2, sd3, nil];
需要将属性名以字符串的形式传递进去并指定根据属性进行升序还是降序排序。最后,所有排序描述符需要放在数组中。
数组中每个排序描述符的位置决定了对象的排序顺序。因此,如果希望数组先根据年
龄,然后根据姓进行排序,那么确保先将与年龄对应的排序描述符添加进来,然后再将与
姓对应的排序描述符添加进来。
要想获得排好序的数组,请向待排序的数组发送 sortedArrayUsingDescriptors:消息并将排序描述符数组作为参数传递进来:
NSArray *sortedArray1 = [listOfObjects sortedArrayUsingDescriptors:sdArray1];
要想查看结果,请使用 makeObjectsPerformSelector:方法将排好序的数组中的每个对象的状态输出到日志中:
[sortedArray1 makeObjectsPerformSelector:@selector(reportState)];
这会以排序描述符(年龄、姓、名)指定的顺序将每个 Person 对象的详细信息打印到日志中。参见程序清单3-5~3-7。
代码
程序清单3-5 Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(strong) NSString *firstName;
@property(strong) NSString *lastName;
@property(assign) int age;
-(id)initWithFirstName:(NSString *)fName lastName:(NSString *)lNameandAge:(int)a;
-(void)reportState;
@end
程序清单3-6 Person.m
#import "Person.h"
@implementation Person
@synthesize firstName, lastName, age;
-(id)initWithFirstName:(NSString *)fName lastName:(NSString *)lNameandAge:(int)a{
self = [super init];
if (self) {
self.firstName = fName;
self.lastName = lName;
self.age = a;
}
return self;
}
-(void)reportState{
NSLog(@"This person's name is %@ %@ who is %i years old", firstName,
lastName, age);
}
@end
程序清单3-7 main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main (int argc, const char * argv[]){
@autoreleasepool {
//Instantiate Person objects and add them all to an array:Person *p1 = [[Person alloc] initWithFirstName:@"Rebecca"
lastName:@"Smith"
andAge:33];
Person *p2 = [[Person alloc] initWithFirstName:@"Albert"
lastName:@"Case"
andAge:24];
Person *p3 = [[Person alloc] initWithFirstName:@"Anton"
lastName:@"Belfey"
andAge:45];
Person *p4 = [[Person alloc] initWithFirstName:@"Tom"
lastName:@"Gun"
andAge:17];
Person *p5 = [[Person alloc] initWithFirstName:@"Cindy"
lastName:@"Lou"
andAge:6];
Person *p6 = [[Person alloc] initWithFirstName:@"Yanno"
lastName:@"Dirst"
andAge:76];
NSArray *listOfObjects = [NSArray arrayWithObjects:p1, p2, p3, p4,
p5, p6, nil];
NSLog(@"PRINT OUT ARRAY UNSORTED");
[listOfObjects makeObjectsPerformSelector:@selector(reportState)];
//Create three sort descriptors and add to an array:NSSortDescriptor *sd1 = [NSSortDescriptor
sortDescriptorWithKey:@"age"ascending:YES];
NSSortDescriptor *sd2 = [NSSortDescriptorsortDescriptorWithKey:@"lastName"ascending:YES];
NSSortDescriptor *sd3 = [NSSortDescriptorsortDescriptorWithKey:@"firstName"ascending:YES];
NSArray *sdArray1 = [NSArray arrayWithObjects:sd1, sd2, sd3, nil];
NSLog(@"PRINT OUT SORTED ARRAY (AGE,LASTNAME,FIRSTNAME)");NSArray *sortedArray1 = [listOfObjects
sortedArrayUsingDescriptors:sdArray1];
[sortedArray1 makeObjectsPerformSelector:@selector(reportState)];NSArray *sdArray2 = [NSArray arrayWithObjects:sd2, sd1, sd3, nil];NSArray *sortedArray2 = [listOfObjects
sortedArrayUsingDescriptors:sdArray2];
NSLog(@"PRINT OUT SORTED ARRAY (LASTNAME,FIRSTNAME,AGE)");[sortedArray2 makeObjectsPerformSelector:@selector(reportState)];
第 3 章 使用对象集合
}
return 0;}
使用
要想使用上述代码,需要为 Person 类创建文件。这是一个 Objective-C 类,可以使用Xcode 文件模板创建。Person 类必须导入到 main.m 文件的代码中(对于 Mac 命令行应用)。构建并运行项目,然后通过控制台日志查看数组的排序结果:
PRINT OUT ARRAY UNSORTED
This person's name is Rebecca Smith who is 33 years oldThis person's name is Albert Case who is 24 years oldThis person's name is Anton Belfey who is 45 years oldThis person's name is Tom Gun who is 17 years old
This person's name is Cindy Lou who is 6 years old
This person's name is Yanno Dirst who is 76 years oldPRINT OUT SORTED ARRAY (AGE,LASTNAME,FIRSTNAME)
This person's name is Cindy Lou who is 6 years old
This person's name is Tom Gun who is 17 years old
This person's name is Albert Case who is 24 years oldThis person's name is Rebecca Smith who is 33 years oldThis person's name is Anton Belfey who is 45 years oldThis person's name is Yanno Dirst who is 76 years oldPRINT OUT SORTED ARRAY (LASTNAME,FIRSTNAME,AGE)
This person's name is Anton Belfey who is 45 years oldThis person's name is Albert Case who is 24 years oldThis person's name is Yanno Dirst who is 76 years oldThis person's name is Tom Gun who is 17 years old
This person's name is Cindy Lou who is 6 years old
This person's name is Rebecca Smith who is 33 years old
3.6 查询数组
问题
你拥有填满了对象的数组,想要根据某些条件(这些条件可以通过 iOS 应用表格中的搜索栏等类似控件进行输入)找出数组的某个子集。
解决方案
你首先需要的是 NSPredicate 对象,NSPredicate 用于定义搜索查询。接下来,可以使用原始数组的filteredArrayUsingPredicate:函数并根据定义的 NSPredicate 对象规范返回数组的子集。
说明
该攻略会用到 3.5 节中使用的 Person 对象。你要定义如下判断,让返回的数组只包含年龄大于 30 的 Person对象:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age > 30"];
判断需要对数组中的对象应用逻辑表达式。这里的 age 必须是待查询数组中对象的某个属性。使用的比较运算符与编程时使用的相同(表 3-2 完整列出了 NSPredicate 比较运算符)。
创建好判断后,你只需要使用 filteredArrayUsingPredicate:函数并将 NSPredicate 对象作为参数传递进去,即可获得数组的子集:
NSArray *arraySubset = [listOfObjects filteredArrayUsingPredicate:predicate];
你会获得另一个数组,该数组只包含与 NSPredicate 对象中所定义规范相匹配的那些对象。参见程序清单 3-8~3-10。
代码
程序清单3-8 Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(strong) NSString *firstName;
@property(strong) NSString *lastName;
@property(assign) int age;
-(id)initWithFirstName:(NSString *)fName lastName:(NSString *)lNameandAge:(int)a;
-(void)reportState;
@end
程序清单3-9 Person.m
#import "Person.h"
@implementation Person
@synthesize firstName, lastName, age;
-(id)initWithFirstName:(NSString *)fName lastName:(NSString *)lNameandAge:(int)a{
self = [super init];
if (self) {
self.firstName = fName;
self.lastName = lName;
self.age = a;
}
return self;
}
-(void)reportState{
NSLog(@"This person's name is %@ %@ who is %i years old", firstName,
lastName, age);
}
@end
程序清单3-10 main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main (int argc, const char * argv[]){
@autoreleasepool {
//Instantiate Person objects and add them all to an array:Person *p1 = [[Person alloc] initWithFirstName:@"Rebecca"
lastName:@"Smith"
andAge:33];
Person *p2 = [[Person alloc] initWithFirstName:@"Albert"
lastName:@"Case"
andAge:24];
Person *p3 = [[Person alloc] initWithFirstName:@"Anton"
lastName:@"Belfey"
andAge:45];
Person *p4 = [[Person alloc] initWithFirstName:@"Tom"
lastName:@"Gun"
andAge:17];
Person *p5 = [[Person alloc] initWithFirstName:@"Cindy"
lastName:@"Lou"
andAge:6];
Person *p6 = [[Person alloc] initWithFirstName:@"Yanno"
lastName:@"Dirst"
andAge:76];
NSArray *listOfObjects = [NSArray arrayWithObjects:p1, p2, p3, p4,p5, p6, nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age > 30"];
NSArray *arraySubset = [listOfObjects
filteredArrayUsingPredicate:predicate];
NSLog(@"PRINT OUT ARRAY SUBSET");
[arraySubset makeObjectsPerformSelector:@selector(reportState)];
}
return 0;}
使用
要想使用上述代码,请从 Xcode 构建并运行 Mac 应用。通过控制台查看根据NSPredicate 对象的查询结果。