前些时间,@Vong 同学在我们知识小集群里发了一段用
NSUserDefaults
存储一个
NSDictionary
字典对象的测试代码,虽然代码看起来似乎很正常,但是运行的时候报错了,根据群里大家的讨论结果,本文整理总结一下。
问题
我们先来看一下这段测试代码:
- (void)test {
NSDictionary *dict = @{@1: @"甲",
@2: @"乙",
@3: @"丙",
@4: @"丁"};
[[NSUserDefaults standardUserDefaults] setObject:dict forKey:@"testKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
上述代码先构建了一个
dict
字典对象,然后把它往
NSUserDefaults
里存储,看起来似乎没什么毛病,但在 Xcode 中运行的时候,却报如下错误:
[User Defaults] Attempt to set a non-property-list object {
3 = "\U4e19";
2 = "\U4e59";
1 = "\U7532";
4 = "\U4e01";
} as an NSUserDefaults/CFPreferences value for key `testKey`
难道是因为上述
NSDictionary
对象的
Key
为
NSNumber
的问题?
NSDictionary
的
Key
一定要为
NSString
吗?显然不是的,我们在上述代码的
dict
初始化后打个断点,然后在命令行里
po dict
,是可以正确看到控制台输出
dict
对象的内容。再来看一下
NSDictionary
的定义:
@interface NSDictionary<KeyType, ObjectType> (NSDictionaryCreation)
- (instancetype)initWithObjects:(NSArray<ObjectType> *)objects forKeys:(NSArray<id<NSCopying>> *)keys;
...
@end
我们可以知道,
NSDictionary
的
Key
只要是遵循
NSCopying
协议的就行,显然
NSNumber
是遵循的(可自行查看
NSNumber
的定义),因此用
NSNumber
作为
NSDictionary
的
Key
是没问题的。
但是,当我们把上述
dict
的
Key
改为字符串的形式(注意跟上面的区别),如下:
NSDictionary *dict = @{@"甲": @1,
@"乙": @2,
@"丙": @3,
@"丁": @4};
此时,
dict
是可以正常存储到
NSUserDefaults
中的。
所以,问题确实是出在
dict
的
Key
上,但到底是什么原因呢?
原因分析
我们需要从
NSUserDefaults
的
setObject:forKey:
方法入手分析:
- (void)setObject:(id)value forKey:(NSString *)defaultName;
通过查看苹果文档:https://developer.apple.com/documentation/foundation/nsuserdefaults/1414067-setobject?language=objc ,有这么一段描述:
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
也就是说,能往
NSUserDefaults
里存储的对象只能是
property list objects
,包括
NSData
,
NSString
,
NSNumber
,
NSDate
,
NSArray
,
NSDictionary
,且对于
NSArray
和
NSDictionary
这两个容器对象,它们所包含的内容也必需是
property list objects
。