在iOS开发中,对于轻量级数据的存储, 可以用NSUserDefaults, 例如:
NSDictionary *dict1 = @{
@1:@"A",
@2:@"B"
};
[[NSUserDefaults standardUserDefaults] setObject:dict1 forKey:@"key1"];
[[NSUserDefaults standardUserDefaults] synchronize];
这种方式可以很方便地对dict进行本地持久化操作, 接下来我们换一种写法:
NSDictionary *dict2 = @{
@1:@"A",
@2:@"B"
};
[[NSUserDefaults standardUserDefaults] setObject:dict2 forKey:@"key1"];
[[NSUserDefaults standardUserDefaults] synchronize];
可以看到运行后直接crash, 错误信息如下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object {
1 = A;
2 = B;
} for key key1'
通过打印dict2 我们可以发现字典的创建方法完全正常, NSDictionary 的 Key 只要是遵循 NSCopying 协议的就行,显然 NSNumber 是遵循的(可自行查看 NSNumber 的定义),因此用 NSNumber 作为 NSDictionary 的 Key 是没问题的, 只是这时候用NSUserDefaults持久化才会遇到这样的问题:
原因分析:
通过查看苹果文档:
https://developer.apple.com/documentation/foundation/nsuserdefaults/1414067-setobject?language=objc,其中有这么一段:
## Discussion
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. For more information, see What is a Property List? in Property List Programming Guide.
Setting a default has no effect on the value returned by the objectForKey: method if the same key exists in a domain that precedes the application domain in the search list.
那么什么是Property List呢 ? 继续看官方文档:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html
这里总结一下,就是说 虽然 NSDictionary 和 CFDictionary 对象的 Key 可以为任何类型(只要遵循 NSCopying 协议即可),但是如果当 Key 不为字符串 string 对象时,此时这个字典对象就不能算是 property list objects 了,所以它就往 NSUserDefaults 中存储,就会报错.
解决
那么对于这种类型的存储, 我们是不是就不能用NSUserDefaults存储呢 ? 答案当然是可以的, 方法有很多, 这里介绍一种,可以在存储前将dict2 转换为所有key都属于string类型再进行存储:
//这里会将key转为string类型
NSMutableDictionary *mutaDict = [NSMutableDictionary dictionary];
[dict2 enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSString *keyStr = [[NSString alloc] init];
keyStr = [NSString stringWithFormat:@"%@",key];
[mutaDict setObject:obj forKey:keyStr];
}];
//此时mutaDict所有的key都属于string类型, 可以直接用NSUserDefaults进行存储
****注意:
这里有些文章推荐用NSData进行存储, 也可以达到预期, 但是data和string的转换两次明显成本要高一点.
拓展
这里拓展一下, 对于非property list 对象, 除了NSUserDefaults会遇到这种情况, 系统其他任何方法涉有及到 Property List 对象,是不是都需要注意上面的问题呢 ?