Saturday, July 24, 2010

Serializing for iPhone, Part II: HelloWorlds With NSCoder

So all this NSUserDefaults stuff is cute, but it's probably the most annoying option. It does handle some of the code for you, but you end up writing it in other places. So let's start again, this time using the NSCoder paradigm 100%. This time we have two objects:

Inner which holds a CGPoint

#import <Foundation/Foundation.h>

@interface Inner : NSObject {

CGPoint point;
}

@property CGPoint point;

@end

and Outer which holds an Inner

#import <Foundation/Foundation.h>
#import "Inner.h"


@interface Outer : NSObject {
Inner *inner;
}

@property (nonatomic, retain)  Inner *inner;

@end

That's it! Now figure it out. I do not want to inform the client class about Outer's structure.

First, without any clue as to how this will work, I implement NSCoding for Outer like so (after making the changes to the .h):

- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.inner forKey:@"inner"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
self.inner = [aDecoder decodeObjectForKey:@"inner"];
return self;
}

and for Inner

- (void)encodeWithCoder:(NSCoder *)aCoder 
[aCoder encodeObject:NSStringFromCGPoint(self.point) forKey:@"point"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
self.point = CGPointFromString([aDecoder decodeObjectForKey:@"point"]);
return self;
}

Now let's just serialize and them, or something.

Outer *outer = [[[Outer alloc] init] autorelease];
outer.inner = [[[Inner alloc] init] autorelease];
outer.inner.point = CGPointMake(525, 525);
BOOL result = [NSKeyedArchiver archiveRootObject:outer toFile:@"somewhereInAppSandbox"];
NSLog(@"how did that go %@", result ? @"yes" : @"no" );

And deserialize them? 

Outer *outer = [NSKeyedUnarchiver unarchiveObjectWithFile:@"somewhereInAppSandbox"];
NSLog(@"moment of truth %@", NSStringFromCGPoint(outer.inner.point));

Wow, this is actually pretty cool. I mean, not as cool as C#'s automatic XML serialization, but I'll take it.

Now... what happens if we house the Outer in an NSArray? 

Outer *outer;
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:@"somewhereInAppSandbox.anArray"];
if (array != nil) {
outer = [array objectAtIndex:0];
NSLog(@"moment of truth %@", NSStringFromCGPoint(outer.inner.point));

outer = [[[Outer alloc] init] autorelease];
outer.inner = [[[Inner alloc] init] autorelease];
outer.inner.point = CGPointMake(525, 525);
array = [[[NSArray alloc] initWithObjects:outer, nil] autorelease];

BOOL result = [NSKeyedArchiver archiveRootObject:array toFile:@"somewhereInAppSandbox.anArray"];
NSLog(@"how did that go %@", result ? @"yes" : @"no" );

Awesome. Now I can have a drink. So what was the point? This way we get an object graph, and each object -- via conforming to the NSCoding protocol --  gets to define how it wants to serialize. And standard property-set objects know how to do their own stuff anyway. Nice!

No comments: