一种简易的客户端存储架构设计

今天看了公司内部某三个APP项目的存储相关代码。总体来说,存储架构基本上是类似的。对此,我绘制了其存储架构的示意图,如下图所示。

数据库

项目使用自定义的数据库KVDBStore,该数据库实际上只是对开源数据库FMDatabase进行了封装,其包含如下两个属性。

1
2
3
4
5
6
@interface KVDBStore()

@property (nonatomic, strong) FMDatabaseQueue *dbQueue;
@property (nonatomic, strong, readonly) NSString *dbPath;

@end

KVDBStore存储于应用沙盒的Data Container中的Documents目录下(进一步了解应用沙盒结构)。该目录可以通过如下方法获取。

1
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

KVDBStore数据库的存储条目具有几个特定的值,使用DBItem对象来表示,其属性包括:

1
2
3
4
5
6
7
@interface DBItem : NSObject

@property (strong, nonatomic) NSString * itemId;
@property (strong, nonatomic) id itemObject;
@property (strong, nonatomic) NSDate * createdTime;

@end

KVDBStore主要提供了构造方法、析构方法以及一些基本的操作方法,如:增删查改、Transaction操作等,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (id)initDBWithName:(NSString *)dbName;

- (id)initWithDBWithPath:(NSString *)dbPath;

- (BOOL)createTableWithName:(NSString *)tableName;

- (BOOL)clearTable:(NSString *)tableName;

- (void)close;

...

///************************ Transaction methods *************************************

- (BOOL)beginTransaction;

- (BOOL)rollbackTransaction;

- (BOOL)commitTransaction;

...

///************************ Put&Get methods *****************************************

- (BOOL)putObject:(id)object withId:(NSString *)objectId intoTable:(NSString *)tableName;

- (id)getObjectById:(NSString *)objectId fromTable:(NSString *)tableName;

- (BOOL)deleteObjectById:(NSString *)objectId fromTable:(NSString *)tableName;

...

KVDBStore是在数据库层面实现的一个类,一个数据库通常是由多个表组成的,在实际开发中,表间的联结操作相对比较少,主要还是对特定表进行增删查改的操作。对此,项目实现了表级类BaseTable以便于进行操作,其包含以下属性:

1
2
3
4
5
6
7
@interface BaseTable ()

@property (nonatomic, strong) NSString *databaseName;
@property (nonatomic, strong) NSString *tableName;
@property (nonatomic, strong) KVDBStore *databaseStore;

@end

存储架构

项目只实例化了一个数据库,所有的表均建立在该数据库中,并且所有的表都继承自BaseTable,而这些表则定义了与该表相关的数据库操作。以AccountTable为例,其应该定义类似以下的方法:

1
2
3
4
5
6
7
- (NSString *)getAccount;
- (void)setAccount:(NSString *)account;

- (UserInfo *)userInfo;
- (void)setUserInfo:(UserInfo *)userInfo;

...

在实际开发中,我们经常会面临同时从(向)一个表(或多个表)读取(写入)数据。对此,项目中实现了各种代理单例来完成这些操作,如:使用AccountAgent定义登录和退出的方法,其内部需要对AccountTable进行很多复杂的操作,。当然,在涉及到表间联结操作时,也应该通过代理来进行实现。

1
2
3
4
5
6
// AccountAgent

- (void)login;
- (void)logout;

...

总结

后续,希望能够阅读以下FMDatabase的源码,以对iOS底层的存储原理有进一步的理解。

(完)