BNRImageStore的属性dictionary是一个指向NSMutableDictionary对象(字典对象)的指针。和数组对象类似,字典对象也是collection对象,也有不可修改的版本(NSDictionary)和可修改的版本(NSMutableDictionary)。数组对象与字典对象的差别是,数组对象包含一组有序的指向对象的指针,可以通过整数索引存取。例如,直接获取数组的第n个元素的代码如下:
// 将某个对象插入数组顶部
[someArray insertObject:someObject atIndex:0];
// 获取刚才插入的对象
someObject = [someArray objectAtIndex:0];
字典对象中的指针不是有序排列的,需要通过键(key)来存取指针,不能使用索引。键其实也是对象,而且最常用的是NSString对象。
// 将某个对象加入NSMutableDictioanry对象,对应的键是“MyKey”
[someDictionary setObject:someObject forKey:@“MyKey”];
// 取回刚才加入的对象
someObject = [someDictionary objectForKey:@“MyKey”];
字典对象是由键-值对(key-value pair)组成的。这里的键是某个不可修改的对象(通常是NSString对象),用来存取另一个与之对应的对象,也就是值。在其他语言中,这里的字典对象称为哈希图(hash map)或哈希表(hash table)。
图11-10 NSDictionary对象图
NSDictionary非常有用,其中最常见的用法是可变数据结构(flexible data structures)和查询表(lookup tables)。
首先介绍可变数据结构。为了在代码中描述一个模型对象,常见的做法是创建一个NSObject的子类,然后添加模型对象的相关属性。例如,对于一个表示“人”的模型对象来说,可以创建一个名为Person的NSObject子类,然后添加姓名、年龄和其他需要的属性。类似地,NSDictionary也可以用来描述模型对象。还是以“人”为例,NSDictionary中可以针对姓名、年龄和其他需要的属性保存相应的键-值对。
使用NSDictionary与NSObject子类Person的区别是,Person要求事先明确定义好“人”的各项属性,并且之后无法添加新的属性,也无法删除或修改现有属性。相反,如果使用NSDictionary,“人”的数据就只是一系列键-值对,操作起来非常简单,例如,为某个人添加“地址”时,只需要为@“address”键设置表示地址的字符串就可以了。
当然,并不是所有的模型对象都可以通过NSDictionary来描述。大部分模型对象具有严格的定义和特定的数据处理方式,不适合采用简单的键-值对管理数据。相反,如果模型对象根据不同的配置选项具有不同的数据结构,就应该使用NSDictionary。例如UIImagePickerController的委托方法imagePickerController:didFinishPicking- MediaWithInfo:,其第二个参数就是一个通过NSDictionary描述的模型对象,根据UIImagePickerController的相关配置,该对象可能包含照片或视频(本章第11.12节会介绍如何使用UIImagePickerController录制视频),以及相关元数据信息。
再介绍NSDictionary的另一个常见用法:查询表。读者在刚开始学习编程时,可能会写出类似如下代码:
- (void)changeCharacterClass:(id)sender
{
NSString *enteredText = textField.text;
CharacterClass *cc = nil;
if ([enteredText isEqualToString:@“Warrior”]) {
cc = knight;
} else if ([enteredText isEqualToString:@“Mage”]) {
cc = wizard;
} else if ([enteredText isEqualToString:@“Thief”]) {
cc = rogue;
}
character.characterClass = cc;
}
当读者需要编写包含大量if-else或switch语句的代码时,通常应该考虑替换为NSDictionary。NSDictionary可以事先在两组对象之间建立一对一的映射关系。例如,上述代码中的if-else语句可以替换为一个NSDictionary对象:
NSMutableDictionary *lookup = [[NSMutableDictionary alloc] init];
[lookup setObject:knight forKey:@“Warrior”];
[lookup setObject:wizard forKey:@“Mage”];
[lookup setObject:rogue forKey:@“Thief”];
有了lookup查询表,changeCharacterClass:方法就可以简化为:
- (void)changeCharacterClass:(id)sender
{
character.characterClass = [lookup objectForKey:textField.text];
}
使用NSDictionary查询表的另一个优点:不需要在方法中硬编码所有数据(角色类型);相反,可以将数据保存在文件系统或远程服务器中,甚至可以由用户动态添加或修改。
BNRImageStore将使用NSDictionary查询表存储照片。BNRImageStore会为每一张照片生成唯一的键,之后可以通过键查找对应的照片。
使用字典对象时,键不能重复。在将某个键-值对加入字典对象时,如果字典对象已经保存了拥有相同的键的值,那么旧的值会被替换掉。如果要用一个键来保存多个对象,则可以先将这些对象存入数组对象,然后将这个数组对象作为值存入字典对象。
与NSArray类似,NSDictionary提供了用于创建对象的简洁语法。请读者回忆NSArray的简洁语法并注意与NSDictionary的区别:NSArray是通过“@”创建的,NSDictionary则是通过“@{}”创建的。
使用简洁语法创建NSDictionary对象时,每一个键值对之间需要使用逗号“,”隔开,而键与值之间则使用冒号“:”隔开,例如,
NSDictionary *dictionary = @{@“key”: object, @“anotherKey”: anotherObject};
另外,NSDictionary也可以使用NSArray中的下标语法,只需要将“”中的序号换成键,就可以读取该键所对应的值,例如,
id object = dictionary[@“key”];
// 与以下代码效果相同
id object = [dictionary objectForKey:@“key”];
而NSMutableDictionary还可以通过下标语法设置键所对应的值:
dictionary[@“key”] = object;
// 与以下代码效果相同
[dictionary setObject:object forKey:@“key”];
下面更新BNRImageStore,使用下标语法存取dictionary中的UIImage对象:
- (void)setImage:(UIImage *)image forKey:(NSString *)key
{
[self.dictionary setObject:image forKey:key];
self.dictionary[key] = image;
}
- (UIImage *)imageForKey:(NSString *)key
{
return [self.dictionary objectForKey:key];
return self.dictionary[key];
}
字典对象的内存管理和数组对象类似。当字典对象加入某个对象后,会成为该对象的拥有方。当字典对象移除某个对象后,会放弃该对象的拥有权。