Storing arbitrary keys/values in iOS Storing arbitrary keys/values in iOS database database

Storing arbitrary keys/values in iOS


I've written an App that deals with 50MB texts(about 10,000 articles in HTML). They're indexed by an articleID. I've used LevelDB, raw Sqlite, Sqlite with FMDB, CoreData and NSUserDefaults in this App.

Here's what I've learned:

LevelDB gives you the arbitrary key/value access pattern and the reading speed is extremely fast if you're reading them sequentially. But if you need to read a key randomly (by not using it's iterator), it is much slower than Sqlite.

Raw Sqlite is fast for random reading if you use it correctly. (Use indexes, cover indexes, etc.)But you will lose the ability to read and write arbitrary key/value.

FMDB gives you a nice Objective-C API but 50% CPU time is wasted when converting data between Objective C and C (NSString <-> char*) (It's still a lot faster than Core Data, 4X+)

Personally I think Core Data sucks. So I won't recommend it to anyone for any usage. Sorry I don't want to recall those pain when using CoreData.

NSUserDefaults is best for user setting and only for user setting, definitely not a solution for long articles.

I use raw Sqlite and Google Protocol Buffer.

Protocol Buffer is extremely fast for encoding and decoding. It also gives you the ability to add new fields by modifying it's .proto file, no DB scheme change is required. You can store several related keys in a single proto, let's say articleMetaData.proto contains the title, author and publishDate, if some time later you need to add modifyDate you just add it to the proto file and store the binary in Sqlite in the same column.


A simple SQLite schema such as ID (indexed) | key | value should give you the freedom you want, with the speed and query capabilities you need. You can use a thin wrapper like FMDB to make it much easier to deal than the C SQLite API.


Personally, rather than using NSUserDefaults which is fine for simple data, I would look into using the NSCoding protocol and NSKeyedArchiver. Have your objects implement the NSCoding protocol and then use something like this to archive/unarchive them:

+ (NSObject *)readArchiveFile:(NSString *)inFileName{    NSFileManager *fileMgr = [NSFileManager defaultManager];    NSString *documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];    NSString *filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectoryPath, inFileName];    NSObject *returnObject = nil;    if( [fileMgr fileExistsAtPath:filePath] )    {        returnObject = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];    }    return returnObject;}+ (void)archiveFile:(NSString *)inFileName inObject:(NSObject *)inObject{    NSString *documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];    NSString *filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectoryPath, inFileName];    BOOL didSucceed = [NSKeyedArchiver archiveRootObject:inObject toFile:filePath];    if( !didSucceed )    {        NSLog(@"File %@ write operation %@", inFileName, didSucceed ? @"success" : @"error" );    }}+ (void)deleteFile:(NSString *)inFileName{    NSFileManager *fileMgr = [NSFileManager defaultManager];    NSString *documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];    NSString *filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectoryPath, inFileName];    NSError *error;    if ( [fileMgr fileExistsAtPath:filePath] && [fileMgr removeItemAtPath:filePath error:&error] != YES)    {        NSLog(@"Unable to delete file: %@", [error localizedDescription]);    }}

Also, I would look into using CoreData as it gives you the ability to query your data.