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.
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.