Bartek Wilczynski bio photo

Bartek Wilczynski

IT consultant and entrepreneur with over 10 years of experience in a variety of medium-size line of business applications, mostly in .NET technology.

Email Twitter LinkedIn Youtube

Serialization

One of the benefits of having the model separated from your view concerns is that you can easily persist your model state to a permanent storage - for example a file or a database. If you want to serialize your model state to a file you can leverage Cocoa archiving which can serialize any object graph and then deserialize it on demand. Unfortunately, when compared to other technologies with automatic serialization built-in, the process is not fully automatic and if you have a very complex model you will need to write a lot of code on your own. Fortunately for most of iPhone games this should not be a big issue - you won't be probably dealing with very complex models and the whole serialization code can be written by hands without much effort.

NSCoding protocol

Model classes should implement NSCoding protocol which contains two methods that needs to be implemented:

- (void)encodeWithCoder:(NSCoder *)coder;
- (id)initWithCoder:(NSCoder *)coder;

The first methods tells the archiver how to serialize your method, the latter one enables retrieving model properties from serialized content.

For the purposes of this post we will be serializing model that contains game options.

@interface Options : NSObject {
}
@property (nonatomic) BOOL playSound;
@property (nonatomic) BOOL playMusic;

+ (id)sharedOptions;
- (void)saveState;

Implementation of NSCoding protocol is quite easy yet a bit tedious if you have more properties than I do:

@implementation Options
@synthesize playSound, playMusic;

#pragma mark NSCoding
- (void)encodeWithCoder:(NSCoder *)coder
{
    [coder encodeBool:self.playSound forKey:@"PlaySound"];
    [coder encodeBool:self.playMusic forKey:@"PlayMusic"];
}
-(id) initWithCoder:(NSCoder *)coder {
    if ((self = [super init])) {
		self.playSound = [coder decodeBoolForKey:@"PlaySound"];
		self.playMusic = [coder decodeBoolForKey:@"PlayMusic"];
	}
    return self;
}
@end

You need to be very careful to keep the same keys in both methods and you also need to remember that properties returned from the coder should be retained if needed.

Serializing and deserializing objects using NSKeyedArchiver and NSKeyedUnarchiver

The last step is performing the serialization using archiver classes. Saving state requires the following:

  1. Creating NSMutableData for storing serialized content,
  2. Instantiating NSKeyedArchiver with instance of created data,
  3. Encoding the object to mutable data instance,
  4. Writing serialized data to file.
@implementation Options
- (void)saveState
{
	NSMutableData *data = [NSMutableData data];
	NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

	[archiver encodeObject:self forKey:OPTIONS_DATA_KEY];

	[archiver finishEncoding];
	[data writeToFile:[Options getArchivePath] atomically:YES];
	[archiver release];
}

+(NSString *) getArchivePath
{
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *documentsDirectory = [paths objectAtIndex:0];
	return [documentsDirectory stringByAppendingPathComponent:OPTIONS_DATA_FILE];
}
@end

Deserialization involves the following actions (I implemented it in my singleton initialize method so I have my options loaded on demand during the first object access):

  1. Reading data from contents of previously saved file to NSData instance,
  2. Instantiating NSKeyedUnarchiver with contents of file,
  3. Decoding object instance from the unarchiver.
@implementation Options
static id _sharedOptions = nil;

+ (id)sharedOptions
{
	if (_sharedOptions == nil) {
		NSData *data = [NSData dataWithContentsOfFile:[Options getArchivePath]];

		if (data) {
			NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
			_sharedOptions = [[unarchiver decodeObjectForKey:OPTIONS_DATA_KEY] retain];

			[unarchiver finishDecoding];
			[unarchiver release];
		}
		else {
			_sharedOptions = [[Options alloc] init];
		}
	}

	return _sharedOptions;
}
@end

Final thoughts

Implementing object serializing in Obj-C is quite easy with archivers. But if you compare it to .NET or Java which can do binary serialization of any object graph (even with cycles) automatically without a need for any code you may feel Cocoa could have been better in this regard. That's it for this post, if you have any remarks or comments feel free to share it in comments.