Jasarien
Jasarien

Reputation: 58458

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!

Upvotes: 10

Views: 6097

Answers (2)

Don McCaughey
Don McCaughey

Reputation: 9982

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.

Upvotes: 21

Becca Royal-Gordon
Becca Royal-Gordon

Reputation: 17861

If you're going to want to retrieve a subset of the objects, SQLite is by far your best choice.

If not, it's a choice between plist format and NSCoding. plist requires you to be able to convert your custom objects into something plist-friendly and then convert the plist back into your objects, but NSCoding is a little harder to wrap your head around and can't be easily edited (or examined) by hand.

Upvotes: 0

Related Questions