-
Notifications
You must be signed in to change notification settings - Fork 363
Storing Objects
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 way 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.)
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 an object class in version 2.0 of your app. No problem. You can handle it automatically within initWithCoder, on the fly, as you fetch objects from the database.
- (id)initWithCoder:(NSCoder *)decoder // NSCoding deserialization
{
if ((self = [super init]))
{
int32_t version = [decoder decodeInt32ForKey:@"version"];
myString = [decoder decodeObjectForKey:@"myString"];
myColor = [decoder decodeObjectForKey:@"myColor"];
myWhatever = [decoder decodeObjectForKey:@"myWhatever"];
myFloat = [decoder decodeFloatForKey:@"myFloat"];
if (version < 2)
myName = [self defaultName]; // myName added in v2
else
myName = [decoder decodeObjectForKey:@"myName"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder // NSCoding serialization
{
[coder encodeInt32:2 forKey:@"version"];
[encoder encodeObject:myString forKey:@"myString"];
[encoder encodeObject:myColor forKey:@"myColor"];
[encoder encodeObject:myWhatever forKey:@"myWhatever"];
[encoder encodeFloat:myFloat forKey:@"myFloat"];
[encoder encodeObject:myName forKey:@"myName"];
}
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".
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;
And you are free to create your own serialization/deserialization routines. Here are some examples of why you might want to do so:
- ENCRYPTION !!! Your custom routine enables on-the-fly encryption/decryption. Now your data-on-disk is secure. And you did it with a few lines of code. And you still get the performance of a database (with built-in object caching). BOOM.
- Compression : Storing big long blobs of text? Use zlib compression to deflate/inflate on-the-fly.
- Performance : If your data has a rigid structure (e.g. you're storing TCP packets), then you may be able to whip up a custom serializer/deserializer routine that performs better and/or takes up less space.