使用 NSUserDefaults 存储字典的一个坑

前些时间,@Vong 同窗在咱们知识小集群里发了一段用 NSUserDefaults 存储一个 NSDictionary 字典对象的测试代码,虽然代码看起来彷佛很正常,可是运行的时候报错了,根据群里你们的讨论结果,本文整理总结一下。html

问题

咱们先来看一下这段测试代码:express

- (void)test {
    NSDictionary *dict = @{@1: @"甲",
                           @2: @"乙",
                           @3: @"丙",
                           @4: @"丁"};
    
    [[NSUserDefaults standardUserDefaults] setObject:dict forKey:@"testKey"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
复制代码

上述代码先构建了一个 dict 字典对象,而后把它往 NSUserDefaults 里存储,看起来彷佛没什么毛病,但在 Xcode 中运行的时候,却报以下错误:bash

[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 对象的 KeyNSNumber 的问题?NSDictionaryKey 必定要为 NSString 吗?显然不是的,咱们在上述代码的 dict 初始化后打个断点,而后在命令行里 po dict,是能够正确看到控制台输出 dict 对象的内容。再来看一下 NSDictionary 的定义:微信

@interface NSDictionary<KeyType, ObjectType> (NSDictionaryCreation)

- (instancetype)initWithObjects:(NSArray<ObjectType> *)objects forKeys:(NSArray<id<NSCopying>> *)keys;

...

@end
复制代码

咱们能够知道,NSDictionaryKey 只要是遵循 NSCopying 协议的就行,显然 NSNumber 是遵循的(可自行查看 NSNumber 的定义),所以用 NSNumber 做为 NSDictionaryKey 是没问题的。app

可是,当咱们把上述 dictKey 改成字符串的形式(注意跟上面的区别),以下:测试

NSDictionary *dict = @{@"甲": @1,
                       @"乙": @2,
                       @"丙": @3,
                       @"丁": @4};
复制代码

此时,dict 是能够正常存储到 NSUserDefaults 中的。ui

因此,问题确实是出在 dictKey 上,但究竟是什么缘由呢?spa

缘由分析

咱们须要从 NSUserDefaultssetObject:forKey: 方法入手分析:命令行

- (void)setObject:(id)value forKey:(NSString *)defaultName;
复制代码

经过查看苹果文档:https://developer.apple.com/documentation/foundation/nsuserdefaults/1414067-setobject?language=objc ,有这么一段描述:rest

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,包括 NSDataNSStringNSNumberNSDateNSArray, NSDictionary,且对于 NSArrayNSDictionary 这两个容器对象,它们所包含的内容也必需是 property list objects

那么,什么是 Property List 呢?咱们能够查看苹果的这份文档:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html

Property lists are based on an abstraction for expressing simple hierarchies of data, ... By convention, each Cocoa and Core Foundation object listed in Table 2-1 is called a property-list object, ...

... If an array or dictionary contains objects that are not property-list objects, then you cannot save and restore the hierarchy of data using the various property-list methods and functions.

And although NSDictionary and CFDictionary objects allow their keys to be objects of any type, if the keys are not string objects, the collections are not property-list objects.

重点看最后一句话,虽然 NSDictionaryCFDictionary 对象的 Key 能够为任何类型(只要遵循 NSCopying 协议便可),可是若是当 Key 不为字符串 string 对象时,此时这个字典对象就不能算是 property list objects 了,因此它就往 NSUserDefaults 中存储,否则就会报错:

Attempt to set a non-property-list objec ... as an NSUserDefaults/CFPreferences value for key ...
复制代码

以上,真相大白。

By the way,不单单是 NSUserDefaultssetObject:forKey: 方法,但凡使用系统其余任何方法涉有及到 Property List 对象,都要注意上面的问题。

扫描关注 知识小集

知识小集是一个团队公众号,每周都会有原创文章分享,咱们的文章都会在公众号首发。欢迎关注查看更多内容。

另外,咱们开了微信群,方便你们沟通交流技术。目前知识小集1号群已满,新开了知识小集2号群,有兴趣的能够扫码加入,没有iOS限制,移动开发的同窗都欢迎。因为微信群扫码加群有100的限制,因此若是扫码没法加入的话,能够先加微信号 coldlight_hh 或者 wsy9871,再拉进群哈。

相关文章
相关标签/搜索