[iOS]How to save and load a custom object?

Haoxiang Li bio photo By Haoxiang Li

Here we go.

How to write a custom object which can be archived to a text file?

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

For example, now we have a customObject:

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

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

@end
</pre>

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");
}
The whole demo can be found here: Demo CustomObject NSCoding Hope it helps! :] ---------------------------------------------------- When we were learning new things, we searched for answers in Google, from Stackoverflow and followed well-written tutorials. When other one faces problems we met and solved, we are glad to answer their questions, post tutorial for the related topics. This is called the spirit of Community. I love it.