NSUserDefaults 存储NSDictionary导致crash延伸到non-property list object

在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

NSUserDefaults 存储NSDictionary导致crash延伸到non-property list object_第1张图片

这里总结一下,就是说 虽然 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 对象,是不是都需要注意上面的问题呢 ?

你可能感兴趣的:(NSUserDefaults 存储NSDictionary导致crash延伸到non-property list object)