Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is NSUserDefaults unwilling to save my NSDictionary?

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?

like image 608
Thomson Comer Avatar asked Feb 24 '12 02:02

Thomson Comer


People also ask

How do you save NSUserDefaults in Objective C?

Saving to NSUserDefaults : Basically, all you have to do is load NSUserDefaults, and then tell it to save a value to a specific key. Here is a simple example of writing an integer to NSUserDefaults: NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setInteger:101 forKey:@”RollNo”];

Is NSUserDefaults persistent?

Preferences stored in NSUserDefaults persist across reboots and relaunches of apps unless otherwise specified.

What is NSUserDefaults?

The NSUserDefaults class provides a programmatic interface for interacting with the defaults system. The defaults system allows an app to customize its behavior to match a user's preferences. For example, you can allow users to specify their preferred units of measurement or media playback speed.


2 Answers

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];
    }
}
like image 106
Thomson Comer Avatar answered Sep 27 '22 03:09

Thomson Comer


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"];
like image 41
leviathan Avatar answered Sep 26 '22 03:09

leviathan