前些時間,@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
物件的 Key
為 NSNumber
的問題?NSDictionary
的 Key
一定要為 NSString
嗎?顯然不是的,我們在上述程式碼的 dict
初始化後打個斷點,然後在命令列裡 po dict
,是可以正確看到控制檯輸出 dict
物件的內容。再來看一下 NSDictionary
的定義:
@interface NSDictionary<KeyType, ObjectType> (NSDictionaryCreation)
- (instancetype)initWithObjects:(NSArray<ObjectType> *)objects forKeys:(NSArray<id<NSCopying>> *)keys;
...
@end
複製程式碼
我們可以知道,NSDictionary
的 Key
只要是遵循 NSCopying
協議的就行,顯然 NSNumber
是遵循的(可自行檢視 NSNumber
的定義),因此用 NSNumber
作為 NSDictionary
的 Key
是沒問題的。
但是,當我們把上述 dict
的 Key
改為字串的形式(注意跟上面的區別),如下:
NSDictionary *dict = @{@"甲": @1,
@"乙": @2,
@"丙": @3,
@"丁": @4};
複製程式碼
此時,dict
是可以正常儲存到 NSUserDefaults
中的。
所以,問題確實是出在 dict
的 Key
上,但到底是什麼原因呢?
原因分析
我們需要從 NSUserDefaults
的 setObject:forKey:
方法入手分析:
- (void)setObject:(id)value forKey:(NSString *)defaultName;
複製程式碼
通過檢視蘋果文件:https://developer.apple.com/documentation/foundation/nsuserdefaults/1414067-setobject?language=objc ,有這麼一段描述:
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
,且對於 NSArray
和 NSDictionary
這兩個容器物件,它們所包含的內容也必需是 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.
重點看最後一句話,雖然 NSDictionary
和 CFDictionary
物件的 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,不僅僅是 NSUserDefaults
的 setObject:forKey:
方法,但凡使用系統其他任何方法涉有及到 Property List
物件,都要注意上面的問題。
掃描關注 知識小集
知識小集是一個團隊公眾號,每週都會有原創文章分享,我們的文章都會在公眾號首發。歡迎關注檢視更多內容。
另外,我們開了微信群,方便大家溝通交流技術。目前知識小集1號群已滿,新開了知識小集2號群,有興趣的可以掃碼加入,沒有iOS限制,移動開發的同學都歡迎。由於微信群掃碼加群有100的限制,所以如果掃碼無法加入的話,可以先加微訊號 coldlight_hh 或者 wsy9871,再拉進群哈。