基于sqlite的key-value存儲工具:YTKKeyValueStore
前言
還記得大學剛學數據庫那會兒,天真地以為世界上所有的存儲都需要用數據庫來做。后來畢業后,正值NOSQL流行,那時我在網易參與了網易微博的開發,我們當時使用了有道自己做的“BigTable”— OMAP來存儲微博數據,那個時候才發現,其實Key-Value這種簡單的存儲也能搞定微博這類不太簡單的存儲邏輯。
相比MYSQL,當數據量上千萬后,NOSQL的優勢體現出來了:對于海量數據,NOSQL在存取速度上沒有任何影響,另外,天生的多備份和分布式,也說數據安全和擴容變得異常容易。
iOS端的嘗試
后來我從后臺轉做iOS端的開發,我就嘗試了在iOS端直接使用Key-Value式的存儲。經過在粉筆網、猿題庫、小猿搜題三個客戶端中的嘗試后,我發現Key-Value式的存儲不但完全能夠滿足大多數移動端開發的需求,而且非常適合移動端采用。主要原因是:移動端存儲的數據量不會很大:
- 如果是單機的應用(例如效率工具Clear),用戶自己一個人創建的數據最多也就上萬條。
- 如果是有服務端的應用(例如網易新聞,微博),那移動端通常不會保存全量的數據,每次會從服務器上獲取數據,本地只是做一些內容的緩存而已,所以也不會有很大的數據量。
如果數據量不大的話,那么在iOS端使用最簡單直接的Key-Value存儲就能帶來開發上的效率優勢。它能保證:
- Model層的代碼編寫簡單,易于測試。
- 由于Value是JSON格式,所以在做Model字段更改時,易于擴展和兼容。
實現方案
在存儲引擎上,2年前我直接選擇了Sqlite當做存儲引擎,相當于每個數據庫表只有Key,Value兩個字段。后來,隨著LevelDB的流行,業界也有一些應用采用了LevelDB來做iOS端的Key-Value存儲引擎,例如開源的ViewFinder。
因為LevelDB本身并不是為移動端設計的,我擔心它過于占用內存,我自己也沒有看到業界有在移動端針對LevelDB做很詳細的測試,連 LevelDB的iOS端移植都不是官方做的。加上我自己寫的基于Sqlite的Key-Value存儲用著也沒有什么問題,所以我也就一直沒有更換成 LevelDB。
開源
經過兩年的使用和測試,我認為它非常好用,而且代碼也非常簡單,只有不到400行。所以現在開源分享給大家,這個項目叫YTKKeyValueStore
,項目在這里。以下是一個簡單的使用示例:
YTKKeyValueStore *store = [[YTKKeyValueStore alloc] initDBWithName:@"test.db"];
NSString *tableName = @"user_table";
[store createTableWithName:tableName];
// 保存
NSString *key = @"1";
NSDictionary *user = @{@"id": @1, @"name": @"tangqiao", @"age": @30};
[store putObject:user withId:key intoTable:tableName];
// 查詢
NSDictionary *queryUser = [store getObjectById:key fromTable:tableName];
NSLog(@"query data result: %@", queryUser);
集成說明
使用本項目,你需要將開源代碼中的YTKKeyValueStore.h
和YTKKeyValueStore.m
添加到你的工程中,并且在工程設置的Link Binary With Libraries
中,增加libsqlite3.dylib
,如下圖所示:
由于時間關系,當前還未提供Cocoapods方式集成。
使用說明
所有的接口都封裝在YTKKeyValueStore
類中。以下是一些常用方法說明。
打開(或創建)數據庫
通過initDBWithName
方法,即可在程序的Document
目錄打開指定的數據庫文件。如果該文件不存在,則會創建一個新的數據庫。
// 打開名為test.db的數據庫,如果該文件不存在,則創新一個新的。
YTKKeyValueStore *store = [[YTKKeyValueStore alloc] initDBWithName:@"test.db"];
創建數據庫表
通過createTableWithName
方法,我們可以在打開的數據庫中創建表,如果表名已經存在,則會忽略該操作。如下所示:
YTKKeyValueStore *store = [[YTKKeyValueStore alloc] initDBWithName:@"test.db"];
NSString *tableName = @"user_table";
// 創建名為user_table的表,如果已存在,則忽略該操作
[store createTableWithName:tableName];
讀寫數據
YTKKeyValueStore
類提供key-value的存儲接口,存入的所有數據需要提供key以及其對應的value,讀取的時候需要提供key來獲得相應的value。
YTKKeyValueStore
類支持的value類型包括:NSString, NSNumber, NSDictionary和NSArray,為此提供了以下接口:
- (void)putString:(NSString *)string withId:(NSString *)stringId intoTable:(NSString *)tableName;
- (void)putNumber:(NSNumber *)number withId:(NSString *)numberId intoTable:(NSString *)tableName;
- (void)putObject:(id)object withId:(NSString *)objectId intoTable:(NSString *)tableName;
與此對應,有以下value為NSString, NSNumber, NSDictionary和NSArray的讀取接口:
- (NSString *)getStringById:(NSString *)stringId fromTable:(NSString *)tableName;
- (NSNumber *)getNumberById:(NSString *)numberId fromTable:(NSString *)tableName;
- (id)getObjectById:(NSString *)objectId fromTable:(NSString *)tableName;
刪除數據接口
YTKKeyValueStore
提供了以下接口用于刪除數據。
// 清除數據表中所有數據
- (void)clearTable:(NSString *)tableName;
// 刪除指定key的數據
- (void)deleteObjectById:(NSString *)objectId fromTable:(NSString *)tableName;
// 批量刪除一組key數組的數據
- (void)deleteObjectsByIdArray:(NSArray *)objectIdArray fromTable:(NSString *)tableName;
// 批量刪除所有帶指定前綴的數據
- (void)deleteObjectsByIdPrefix:(NSString *)objectIdPrefix fromTable:(NSString *)tableName;
更多接口
YTKKeyValueStore
還提供了以下接口來獲取表示內部存儲的key-value對象。
// 獲得指定key的數據
- (YTKKeyValueItem *)getYTKKeyValueItemById:(NSString *)objectId fromTable:(NSString *)tableName;
// 獲得所有數據
- (NSArray *)getAllItemsFromTable:(NSString *)tableName;
由于YTKKeyValueItem
類帶有createdTime
字段,可以獲得該條數據的插入(或更新)時間,以便上層做復雜的處理(例如用來做緩存過期邏輯)。
其它
兩年前寫過不少測試用例,后來給弄丟了,所以現在開項項目中還沒有測試用例。由于時間關系,更詳細的使用說明稍后會更新到項目中。