使用 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,再拉進羣哈。

相關文章
相關標籤/搜索