内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

使用NSUserDefaults存储NSDictionary的一个坑

2018-04-16 12:18 出处:清屏网 人气: 评论(0

前两天,@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 对象的 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 是没问题的。

但是,当我们把上述 dictKey 改为字符串的形式(注意跟上面的区别),如下:

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

此时, dict 是可以正常存储到 NSUserDefaults 中的。

所以,问题确实是出在 dictKey 上,但到底是什么原因呢?

原因分析

我们需要从 NSUserDefaultssetObject:forKey: 方法入手分析:

- (void)setObject:(id)value forKey:(NSString *)defaultName;

通过查看 苹果文档 ,有这么一段描述:

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

那么,什么是 Property List 呢?我们可以查看苹果的 这份文档

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 对象,都要注意上面的问题。

分享给小伙伴们:
本文标签: NSUserDefaulNSDictionary

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号