在iOS中,只要是不为nil
的OC不可变对象类型都可以作为NSDictionary<KeyType, ObjectType>的KeyType。比如NSNumber
,NSArray
,NSMutableArray
,NSDictionary
等等。但是自定义对象作为key的时候必须遵守<NSCopying>
协议并实现其协议方法- (id)copyWithZone:(NSZone *)zone
,如下:
- (id)copyWithZone:(NSZone *)zone{
//这里必须是self本身对象,具体原因请下翻
return self;
}
一、探究
首先任意对象可以作为NSDictionary的Value。这个我相信大家已经熟烂于心。所以下边的示例中,NSDictionary的Value我只用了一个字符串。
-
NSNumber作为Key进行试验
-
NSArray作为Key进行试验
-
NSDictionary作为Key进行试验
-
NSMutableArray作为Key进行试验
-
自定义对象作为Key进行试验(记得实现<NSCopying>的
copyWithZone
)
二、原理
-
NSDictionary苹果系统实现原理
Objective-C 中的字典 NSDictionary 底层其实是一个哈希表,实际上绝大多数语言中字典都通过哈希表实现。
NSDictionary是使用 hash表来实现key和value之间的映射和存储的, hash函数设计的好坏影响着数据的查找访问效率。数据在hash表中分布的越均匀,其访问效率越高。而在Objective-C中,通常都是利用NSString 来作为键值,其内部使用的hash函数也是通过使用 NSString对象作为键值来保证数据的各个节点在hash表中均匀分布。
字典每个条目存取是通过将字典的键(Key)计算出键的hash值,通过查hash表获取具体的value。所以作为NSDictionary 的键(Key)取值的时候,只要其key对象内容地址相同就可以取出相应的值。(PS:地址不同,对象相同的遵守<NSCopying>
协议的key也可以取出对应的value)。
-
从官方文档一些细节中体现
- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey
从这个方法中可以知道, 要作为 Key 值,必须遵循 NSCopying 协议。也就是说在NSDictionary内部,会对 aKey 对象 copy 一份新的。而 anObject 对象在其内部是作为强引用(retain或strong)。所以在MRC下,向该方法发送消息之后,我们会向anObject发送 release 消息进行释放
三、注意事项
1.自定义对象作为key必须遵守<NSCopying>
协议并实现其协议方法- (id)copyWithZone:(NSZone *)zone
. 切记 切记 切记!
- (id)copyWithZone:(NSZone *)zone{
return self;
}
2.尽量使用NSString
- 一般来说,如果你用苹果的plist来储存文件的话,那么key只能是NSString格式,使用NSNumber格式的话,
-writeToFile
是写不进去的,会返回值NO - Classes such as
NSString
that are part of Foundation have a good hash function.
3.苹果的官方文档(部分摘要)指出:
A key can be any object that adopts the NSCopying protocol and implements the
hash and isEqual: methods.
If the key objects have a good hash function, accessing an element,
setting an element, and removing an element all take constant time. With a poor
hash function (one that causes frequent hash collisions), these operations
take up to linear time. Classes such as `NSString` that are part of Foundation
have a good hash function.
笔者拙劣的翻译:
OC对象作为字典的Key需要遵守
NSCopying
协议并且实现hash
和isEqual
两个方法。如果有一个好的hash性能的OC对象作为字典的Key,当访问、修改、移除一个条目所耗费的时间都是基本固定的。如果作为key的对象的hash性能很低(就是hash值经常冲突),上边那些操作所耗费的时间将呈先行增长。举个例子来说,Foundation 框架中的
NSString
的hash性能就很不错。
- 上边我提到的说是必须(注意是必须)遵守协议<NSCopying>并实现
(id)copyWithZone:(NSZone *)zone
。这是保证程序不会一运行就crash。更完备性的就是如官网文档第三段所说将isEqual
和hash
两个方法一起重写。 - NSDictionary苹果官网链接
4.有些朋友可能阅读上边例子的时候对 @[]
或者@{}
作为Key会有一些疑问
@[]、@{}、@3 这三个快捷类型在内存中的地址只有一份。所以上边可以正常的取出值。