hint: NSUserDefaults - storing custom objects with NSDictionary

14 Jun 2012

Using [NSUserDefaults standardUserDefaults] for storing some local data has became a very often practice. However, you are able to successfully store only native data types such as:

1
2
3
4
5
6
NSData
NSString
NSNumber
NSDate
NSArray
NSDictionary

Here's a hint how to handle serialization / deserialization in a bit different way, that you can reuse with Web services. <!-- more -->

The original solution

proposed by Apple is that you implement

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeObject:self.surname forKey:@"surname"];
}

- (instancetype)initWithCoder:(NSCoder *)decoder {
    self = [super initWithCoder:decoder];
    if (self) {
        _name = [decoder decodeObjectForKey:@"name"];
        _surname = [decoder decodeObjectForKey:@"surname"];
    }
    return self;
}

And then when you want to store / retrieve it:

1
2
3
4
5
6
7
8
//storing
Person *person  = //get the person
NSData *encodedPerson = [NSKeyedArchiver archivedDataWithRootObject:person];
[[NSUserDefaults standardUserDefaults] setObject:myEncodedObject forKey:@"encodedPersonKey"];

//retrieving
NSData *encodedPerson = [[NSUserDefaults standardUserDefaults] objectForKey:@"encodedPersonKey"];
Person *person = (Person *)[NSKeyedUnarchiver unarchiveObjectWithData:encodedPerson];

Basically, you're converting your object here into NSData and storing / reading it like that.

What this post is about - we'll use the NSDictionary instead of NSData for doing the same job. Let's assume we have a Web service, and we're receiving a Person (or people) remotely.

When they're converted using the JSON (or XML) frameworks, they're becoming an array of NSDictionaries. Our app is object oriented, so we don't want to access the Person all around the app with

1
personDict[@"name"];

The proper way would be to have an Person object with it's properties, so we would use it everywhere like

1
2
3
Person *person = //get the person
person.name
person.surname

To achieve that point, we need to deserialize the Person from NSDictionary received from JSON. So, in addition to the code from the top, we would need also:

1
2
3
4
5
6
7
8
- (id)initWithDictionary:(NSDictionary *)personDict {
    self = [super init];
    if (self) {
        self.name = personDict[@"name"];
        self.surname = personDict[@"surname"];
    }
    return self;
}

By that, we already have 2 deserialization methods. Also, if we want to send the modified object back to the server, we'll need to add one more serialization method, that'll convert it back to NSDictionary , and then to JSON.

NSDictionary only solution

The only trick here is that we provide an -toDictionary method. As we already have -initWithDictionary:, this means we can serialize/deserialize to and from JSON and NSUserdefaults.

1
2
3
4
5
6
- (NSDictionary *)toDictionary {
    return @{
        @"first_name": self.name,
        @"last_name": self.surname
    };
}

To sum up, you can

  • save / load the custom object with:
1
2
3
4
5
//save
[[NSUserDefaults standardUserDefaults] setObject:[person toDictionary]] forKey:@"personKey"];

//load
[[NSUserDefaults standardUserDefaults] objectForKey:@"personKey"];


- receive / send the object from a web service

1
2
3
4
5
NSDictionary *personFromJSON = //get the person
Person *person = [[Person alloc] initWithDictionary:personFromJSON];

person.surname = @"modifiedSurname";
[person toDictionary]; // dict - you can convert it back to JSON