How to save and load a custom object?

How to Save & Load Custom Object ?

Usually, we use NSKeyedArchiver to serialize an object and write it to a file.
Correspondingly, NSKeyedUnarchiver is used to get the object from the file.

The NSKeyedArchiver’s interface is like this:

+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;

NSKeyedArchiver is a NSCoder. The rootObject should conform to the protocol NSCoding.

@protocol NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;

@end

Lets Take One Example : 
For example, now we have a customObject:

@interface CustomObject : NSObject <NSCoding> {
NSString* mStringValue;
int mIntValue;
BOOL mBOOLValue;
}

@property (non-atomic, retain) NSString *stringValue;
@property (nonatomic, assign) int intValue;
@property (nonatomic, assign) BOOL boolValue;

@end

It conforms to NSCoding, so we need to implement those two methods in the .m file

#define kEncodeKeyStringValue   @"kEncodeKeyStringValue" 
#define kEncodeKeyIntValue @"kEncodeKeyIntValue"
#define kEncodeKeyBOOLValue @"kEncodeKeyBOOLValue"

#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.stringValue forKey:kEncodeKeyStringValue];
[aCoder encodeInt:self.intValue forKey:kEncodeKeyIntValue];
[aCoder encodeBool:self.boolValue forKey:kEncodeKeyBOOLValue];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super init]))
{
self.stringValue = [aDecoder decodeObjectForKey:kEncodeKeyStringValue];
self.intValue = [aDecoder decodeIntForKey:kEncodeKeyIntValue];
self.boolValue = [aDecoder decodeBoolForKey:kEncodeKeyBOOLValue];
}
return self;
}

Now, we can save and load a customObject like:

- (IBAction)saveObjectPlain:(id)sender {

printf("==========================================\n");
printf("saveObjectPlain===========================\n");
printf("==========================================\n");

//< Create and Save the Object
{
CustomObject *obj = [[[CustomObject alloc] init] autorelease];
obj.stringValue = @"The String Value";
obj.intValue = 12345;
obj.boolValue = YES;
[NSKeyedArchiver archiveRootObject:obj toFile:[self tempFilePath]];
printf("Save: \n %s \n", [[obj description] cStringUsingEncoding:NSUTF8StringEncoding]);
}

//< Load and Print the Object
{
CustomObject *obj = [NSKeyedUnarchiver unarchiveObjectWithFile:[self tempFilePath]];
printf("Load: \n %s \n", [[obj description] cStringUsingEncoding:NSUTF8StringEncoding]);
}

printf("==========================================\n");
}

Quite easy, right?

How about to save/load an array of our CustomObjects?
Since NSArray conforms to protocol NSCoding, it is quite straightforward.

- (IBAction)saveObjectsInArray:(id)sender {

printf("==========================================\n");
printf("saveObjectsInArray========================\n");
printf("==========================================\n");

//< Create Two Keys and Save the Object
{
CustomObject *obj1 = [[[CustomObject alloc] init] autorelease];
obj1.stringValue = @"The String Value 1";
obj1.intValue = 12345;
obj1.boolValue = YES;

CustomObject *obj2 = [[[CustomObject alloc] init] autorelease];
obj2.stringValue = @"The String Value 2";
obj2.intValue = 54321;
obj2.boolValue = NO;

NSArray *array = [NSArray arrayWithObjects:obj1, obj2, nil];
[NSKeyedArchiver archiveRootObject:array toFile:[self tempFilePath]];

printf("Save: \n %s \n ", [[array description] cStringUsingEncoding:NSUTF8StringEncoding]);
}

//< Load and Print the Object
{
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:[self tempFilePath]];
printf("Load: \n %s \n ", [[array description] cStringUsingEncoding:NSUTF8StringEncoding]);
}

printf("==========================================\n");
}

So, if there is a member variable of type NSArray in our CustomObject,
we need to make sure the array contains only objects conform to NSCoding.
Otherwise, an exception of unrecognized selector [.. encodeWithCoder:] will be raised.

One step further, how to save/load a NSDictionary has a CustomObject as the key and another CustomObject as the Object.

This is actually another problem. We need to make sure CustomObject can be used as a key in the NSDictionary.
According to the documentation, NSDictionary requires its key to conform to the protocol NSCopying.

@protocol NSCopying

- (id)copyWithZone:(NSZone *)zone;

@end

The key object needs to be copied to a specified piece of memory described by NSZone.
The implementation is like:

#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
CustomObject *copy = [[CustomObject allocWithZone:zone] init];
copy.stringValue = [[self.stringValue copy] autorelease];
copy.intValue = self.intValue;
copy.boolValue = self.boolValue;
return copy;
}

We use allocWithZone to allocate a CustomObject on the indicated piece of memory.
Then copy member variables to the new instance.

The save/load procedure is like:

- (IBAction)saveObjectAsKey:(id)sender {

printf("==========================================\n");
printf("saveObjectAsKey===========================\n");
printf("==========================================\n");

//< Create Two Keys and Save the Object
{
CustomObject *keyObj = [[[CustomObject alloc] init] autorelease];
keyObj.stringValue = @"The String Value Key";
keyObj.intValue = 12345;
keyObj.boolValue = YES;

CustomObject *valObj = [[[CustomObject alloc] init] autorelease];
valObj.stringValue = @"The String Value Value";
valObj.intValue = 54321;
valObj.boolValue = NO;

NSDictionary *dict = [NSDictionary dictionaryWithObject:valObj forKey:keyObj];
[NSKeyedArchiver archiveRootObject:dict toFile:[self tempFilePath]];

printf("Save: \n %s \n", [[dict description] cStringUsingEncoding:NSUTF8StringEncoding]);
}

//< Load and Print the Object
{
NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithFile:[self tempFilePath]];
printf("Load: \n %s \n", [[dict description] cStringUsingEncoding:NSUTF8StringEncoding]);
}

printf("==========================================\n");
}

Hope it helps! :)