in Article

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

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 <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");
}

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.

  • http://www.sigma.me Sigma

    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.==========================
    赞这段话,话说我打出来的字直接看不多,和留言版的背景都是白色。。。不太方便啊

  • http://www.sigma.me Sigma

    那个文字是白色的原因貌似是因为我前面考了一段你文章的白色文字,导致字体变白了,之后手写的也是白色看不见。。。
    直接在回复中打字的话是黑色的

  • Remain Anonymous

    Great article. It really helped understand the things involved. Thanks for a great effort.

  • Pingback: How to save and load a custom object » iFun5

  • Tim

    Brilliant post, the 4 tutorials I tried before this didn’t make sense because they missed the ‘saveObjectAsKey’ method – finally this totally nailed it!

  • chiuan

    thanks for sharing!

  • Hemen Gohil

    Great thanks.
    damn happy for this.
    i have faced issue in that i want to store the whole objects ( Control Objects as well ) in file i have tried hard to solve but i haven’t got any help from any one or from any other example.

    Above example have solved my issue. :-)

  • general

    #import

    @interface Person : NSObject{

    NSString *mName;

    NSString *mSex;

    }

    @property(retain,nonatomic) NSString *name,*sex;

    @end

    #import “Person.h”

    #define nameString @”name”

    #define sexString @”sex”

    @implementation Person

    @synthesize name,sex;

    -(void) encodeWithCoder:(NSCoder *)aCoder{

    [aCoder encodeObject:self.name forKey:nameString];

    [aCoder encodeObject:self.sex forKey:sexString];

    }

    -(id) initWithCoder:(NSCoder *)aDecoder{

    self = [super init];

    if(self){

    self.name = [aDecoder decodeObjectForKey:nameString];

    self.sex = [aDecoder decodeObjectForKey:sexString];

    }

    return self;

    }

    @end

    Person *p = [[Person alloc] init];

    p.name = @”lq”;

    p.sex = @”mail”;

    if([NSKeyedArchiver archiveRootObject:p toFile:@"fffff"]){

    Person *p2 = [NSKeyedUnarchiver unarchiveObjectWithFile:@"fffff"];

    NSLog(@”%@–%@”,p2.name,p2.sex);

    }else{

    NSLog(@”failed”);

    }

    It didn’t work,why?

  • Bogdan Coticopol

    thank you for your tutorial, it’s exactly what i needed!