Skip to content
robbiehanson edited this page Mar 21, 2013 · 19 revisions

The Basics

You can store any kind of object you want using YapDatabase!

In order to store an object to disk (via YapDatabase or any other protocol) you need some method of serializing the object. That is, convert the object into a big blob of bytes. And then, to get your object back from the disk you deserialize it (convert big blob of bytes back into object form).

With YapDatabase, you can choose the default serialization/deserialization process, or you can customize it and use your own routines. (In fact, you can have different routines for objects vs metadata.)


The Default Setup

First, let's look at the default serializer & deserializer: NSCoding.

NSCoding is an Apple protocol used to serialize/deserialize an object, and its already supported by a lot of the classes you're already using:

  • NSString
  • NSNumber
  • NSArray
  • NSDictionary
  • NSSet
  • NSData
  • UIColor
  • etc...

So if you want to store objects in the database that already support NSCoding (such as strings, numbers, etc), then you don't have to do a thing. You're good to go.

Otherwise all you have to do is make sure your custom objects implement the 2 NSCoding methods. Before I show you how easy this is, I'll give you an additional link to Apple's thorough documentation.

@interface MyObject : NSObject <NSCoding>
@end

@implementation MyObject
{
	NSString *myString;
	UIColor *myColor;
	MyWhatever *myWhatever;
	float myFloat;
}

- (id)init // Normal init method
{
	if ((self = [super init])) {
		// ...
	}
	return self;
}

- (id)initWithCoder:(NSCoder *)decoder // NSCoding deserialization
{
	if ((self = [super init])) {
		myString = [decoder decodeObjectForKey:@"myString"];
		myColor = [decoder decodeObjectForKey:@"myColor"];
		myWhatever = [decoder decodeObjectForKey:@"myWhatever"];
		myFloat = [decoder decodeFloatForKey:@"myFloat"];
	}
	return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder // NSCoding serialization
{
	[encoder encodeObject:myString forKey:@"myString"];
	[encoder encodeObject:myColor forKey:@"myColor"];
	[encoder encodeObject:myWhatever forKey:@"myWhatever"];
	[encoder encodeFloat:myFloat forKey:@"myFloat"];
}

Not exactly rocket science is it? But what about that MyWhatever ivar?

It's simple. If MyWhatever supports NSCoding, then it's initWithCoder/encodeWithCoder method will be called. And everything just works.

It works the same way with arrays. If you have an NSArray of MyObject's, then you can just pass the array to the database. The initWithCoder/encodeWithCoder method of NSArray calls down to the corresponding method of its objects. So you can have arrays (or dictionaries or sets or whatever) that contain your custom objects. And your custom objects can have their own object graph.

One of the hidden benefits of custom serialization/deserialization over Core Data is that you can perform upgrades on the fly. For example, imagine that you need to make changes to your objects in version 2.0 of your app. Maybe you remove an ivar, and add a few others, and they need to have default values. No problem, it gets handled within initWithCoder, on the fly, as you fetch objects from the database. This is in contrast to Core Data, which is going to incur a performance hit when the user first upgrades, as it adds the proper columns, and then loops over every row in the database to pre-populate the values. Not the hiccup you want when your release notes brag about "performance improvements".


The Details

Both YapDatabase and YapCollectionsDatabase have the following init methods:

- (id)initWithPath:(NSString *)path;

- (id)initWithPath:(NSString *)path
        serializer:(NSData *(^)(id object))serializer
      deserializer:(id (^)(NSData *))deserializer;

- (id)initWithPath:(NSString *)path objectSerializer:(NSData *(^)(id object))objectSerializer
                                  objectDeserializer:(id (^)(NSData *))objectDeserializer
                                  metadataSerializer:(NSData *(^)(id object))metadataSerializer
                                metadataDeserializer:(id (^)(NSData *))metadataDeserializer;

As you can see, the serialization & deserialization process is customizable. In fact, you can have different setups for objects vs metadata.

You already know the default serializer/deserializer uses NSCoding. There are other options available out of the box.

/**
 * The default serializer & deserializer use NSCoding (NSKeyedArchiver & NSKeyedUnarchiver).
 * Thus any objects that support the NSCoding protocol may be used.
 *
 * Many of Apple's primary data types support NSCoding out of the box.
 * It's easy to add NSCoding support to your own custom objects.
**/
+ (NSData *(^)(id object))defaultSerializer;
+ (id (^)(NSData *))defaultDeserializer;

/**
 * Property lists ONLY support the following: NSData, NSString, NSArray, NSDictionary, NSDate, and NSNumber.
 * Property lists are highly optimized and are used extensively Apple.
 * 
 * Property lists make a good fit when your existing code already uses them,
 * such as replacing NSUserDefaults with a database.
**/
+ (NSData *(^)(id object))propertyListSerializer;
+ (id (^)(NSData *))propertyListDeserializer;

/**
 * A FASTER serializer & deserializer than the default, if serializing ONLY a NSDate object.
 * You may want to use timestampSerializer & timestampDeserializer if your metadata is simply an NSDate.
**/
+ (NSData *(^)(id object))timestampSerializer;
+ (id (^)(NSData *))timestampDeserializer;

These options will cover the normal use cases. But there are always other use cases.

For example, if your data has a very rigid structure (e.g. you're storing tcp packets), then you can likely whip up a custom serializer/deserializer routine that performs much better.

Clone this wiki locally