Search code examples
objective-ciphonepersistenceplist

Persisting Custom Objects


I have a custom object that simply inherits from NSObject. It has 3 members - two floats and an NSDate.

My app is going to have an array with a number of these objects kicking around, and I need to persist it between runs. How can I accomplish this?

I've thought about using a SQLite db, but I'm thinking that it's a bit overkill since the only query I'd ever do would be select *.

In an ideal world I'd like to use an xml plist file. I'm not sure if I can do this with my custom object though. I know there's a set of Property List objects, and that NSArray comes under that, but writeToFile:atomically: only works with property list objects.

Any thoughts would be appreciated, thanks!


Solution

  • NSCoding will do exactly what you want. I recommend you read up on it in the Apple docs, but I thought it was pretty straightforward to use. Your class (and any child classes) will need to implement the NSCoding protocol and you'll need to add -encodeWithCoder: and -initWithCoder: methods to your objects. Most of the common framework classes implement NSCoding already.

    The code for your class will look something like this:

    -(void) encodeWithCoder: (NSCoder*) coder {
      [coder encodeInteger: versionValue forKey: versionKey];
      [coder encodeObject: myStuff forKey: myStuffKey];
    }
    
    -(id) initWithCoder: (NSCoder*) coder {
      self = [super init];
      if ( ! self) return nil;
      myStuff = [[coder decodeObjectForKey: myStuffKey] retain];
      return self;
    }
    

    It's recommended you add a version number when encoding to give you flexibility to manage changes to your archive format in future versions.

    In my class, I added a convenience method to archive my object:

    -(void) archiveToFile: (NSString*) path {
      NSMutableData *data = [[NSMutableData alloc] init];
      NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData: data];
      [archiver encodeObject: self forKey: myArchiveKey];
      [archiver finishEncoding];
      [archiver release];
      [data writeToFile: path atomically: YES];
      [data release];
    }
    

    and another one to unarchive or create a new object:

    +(MyArchive*) newFromFile: (NSString*) path
                orWithMyStuff: (MyStuff*) myStuff
    {
      NSData *data = [[NSData alloc] initWithContentsOfFile: path];
      NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData: data];
      MyArchive *myArchive = [unarchiver decodeObjectForKey: myArchiveKey];
      [unarchiver finishDecoding];
    
      if (myArchive) {
        [myArchive retain];
      } else {
        myArchive = [[MyArchive alloc] initWithStuff: myStuff;
      }
      [unarchiver release];
      [data release];
      return myArchive;
    }
    

    Since your top level object is an NSArray, you'll need to modify the last two methods for your case, but most of the boilerplate code will be the same.