I'm using KVC to serialize an NSObject
and attempt to save it to NSUserDefaults
, which is giving me an Attempt to insert non-property value
when I try to store my NSDictionary
.
Following are the properties of the object in question, MyClass
:
@interface MyClass : NSObject
@property (copy,nonatomic) NSNumber* value1;
@property (copy,nonatomic) NSNumber* value2;
@property (copy,nonatomic) NSString* value3;
@property (copy,nonatomic) NSString* value4;
@property (copy,nonatomic) NSString* value5;
@property (copy,nonatomic) NSString* value6;
@property (copy,nonatomic) NSString* value7;
@property (copy,nonatomic) NSString* value8;
@end
When it is time to save MyClass it occurs here:
-(void)saveMyClass
{
NSArray* keys = [NSArray arrayWithObjects:
@"value1",
@"value2",
@"value3",
@"value4",
@"value5",
@"value6",
@"value7",
@"value8",
nil];
NSDictionary* dict = [self dictionaryWithValuesForKeys:keys];
for( id key in [dict allKeys] )
{
NSLog(@"%@ %@",[key class],[[dict objectForKey:key] class]);
}
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:dict forKey:[NSString stringWithString:kMyClassKey]];
[defaults synchronize];
}
which produces this output:
2012-02-23 19:35:27.518 MyApp[10230:40b] __NSCFConstantString __NSCFNumber
2012-02-23 19:35:27.519 MyApp[10230:40b] __NSCFConstantString __NSCFNumber
2012-02-23 19:35:27.519 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.519 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.520 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.520 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.520 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.520 MyApp[10230:40b] __NSCFConstantString NSNull
2012-02-23 18:38:48.489 MyApp[9709:40b] *** -[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '{
value1 = "http://www.google.com";
value2 = "MyClassData";
value3 = 8;
value4 = "<null>";
value5 = "http://www.google.com";
value6 = 1;
value7 = "http://www.google.com";
value8 = 4SY8KcTSGeKuKs7s;
}' of class '__NSCFDictionary'. Note that dictionaries and arrays in property lists must also contain only property values.`
As you can see, all of the objects in the dict
are property list values and all of its keys are NSString*
. What trivia am I lacking in order to execute this? Or should I give up and use writeToFile
or similar?
Props to Kevin who suggested printing the values, of course one of which is of type NSNull
which is not a property list value. Thanks!
The kludgy solution to my problem - iterate over the keys of the dictionary I just produced so conveniently with dictionaryWithValuesForKeys
and remove those of type NSNull
. sigh
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithDictionary:[self dictionaryWithValuesForKeys:keys]];
for( id key in [dict allKeys] )
{
if( [[dict valueForKey:key] isKindOfClass:[NSNull class]] )
{
// doesn't work - values that are entered will never be removed from NSUserDefaults
//[dict removeObjectForKey:key];
[dict setObject@"" forKey:key];
}
}
I usually archive and unarchive dictionaries when saving them to the user defaults.
This way you don't have to manually check for NSNull
values.
Just add the following two methods to your code. Potentially in a category.
- (BOOL)archive:(NSDictionary *)dict withKey:(NSString *)key {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = nil;
if (dict) {
data = [NSKeyedArchiver archivedDataWithRootObject:dict];
}
[defaults setObject:data forKey:key];
return [defaults synchronize];
}
- (NSDictionary *)unarchiveForKey:(NSString *)key {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:key];
NSDictionary *userDict = nil;
if (data) {
userDict = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
return userDict;
}
Then you can archive any dictionary like this (assuming the method are available in the class):
NSDictionary *dict = ...;
[self archive:dict withKey:@"a key of your choice"];
and retrieve it later on again like this:
NSDictionary *dict = [self unarchiveForKey:@"a key of your choice"];
If you need to store;
- data from a custom object,
- or an array of custom objects
you can use NSKeyedArchiver methods. You can check leviathan's answer for this method.
However, if you are trying to store;
- a dictionary that contains either NSString or NSNumber (like a dictionary converted from a JSON service response),
- or array of this kind of dictionary
you don't need to use NSKeyedArchiver. You can use user defaults.
In my case, when I retrieve the dictionary from user defaults it was returning nil, so I thought NSUserDefaults is unwilling to save my dictionary.
However, it was saved, but I was using the wrong getter method to retrieve it from user defaults;
[[NSUserDefaults standardUserDefaults] stringForKey:<my_key>]
Please make sure you used either;
[[NSUserDefaults standardUserDefaults] dictionaryForKey:<my_key>]
or;
[[NSUserDefaults standardUserDefaults] objectForKey:<my_key>]
before checking any other possible reason.
来源:https://stackoverflow.com/questions/9424517/why-is-nsuserdefaults-unwilling-to-save-my-nsdictionary