-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Objc 数据库、表、事务
本文主要介绍 WCDB Objc 的三个基础概念:数据库 - WCTDatabase
、表 - WCTTable
和 事务。
WCTDatabase
是 WCDB Objc 中最基础的类,几乎所有操作都由该类发起。
WCTDatabase
可以通过文件路径来创建,而且 WCDB 会自动创建中间路径上的文件夹:
WCTDatabase* database = [[WCTDatabase alloc] initWithPath:@"~/Intermediate/Directories/Will/Be/Created/sample.db"];
通过设置标签,可以区分不同的数据库。
int myTag1 = 1;
WCTDatabase* database1 = [[WCTDatabase alloc] initWithPath:path1];
database1.tag = myTag1;
int myTag2 = 2;
WCTDatabase* database2 = [[WCTDatabase alloc] initWithPath:path2];
database2.tag = myTag2;
Tag
不会实际保存到文件中,但因为同一个路径的数据库会共享同一份底层内存数据,所以同一个路径的WCTDatabase
会共享同一个 tag
。
NSLog(@"%ld", database1.tag); // 输出 1
WCTDatabase* anotherDatabase1 = [[WCTDatabase alloc] initWithPath:path1];
NSLog(@"%ld", anotherDatabase1.tag); // 输出 1
延迟初始化是 WCDB 的原则之一,绝大部分数据只会在需要用到时才创建并初始化。数据库的打开就是其中一个例子。
数据库会在第一次进行操作时,自动打开并初始化。开发者不需要手动调用。
WCTDatabase* database = [[WCTDatabase alloc] initWithPath:filePath];
NSLog(@"%d", database.isOpened); // 输出 0
BOOL ret = [database createTable:@"sampleTable" withClass:Sample.class];// 数据库此时会被自动打开
NSLog(@"%d", database.isOpened); // 输出 1
同时,也可以通过 canOpen
接口测试数据库能否正常打开。
WCTDatabase* database1 = [[WCTDatabase alloc] initWithPath:filePath];
NSLog(@"%d", database1.isOpened); // 输出 0
NSLog(@"%d", database1.canOpen); // 输出 1。仅当数据库无法打开时,如路径无法创建等,该接口会返回 NO
NSLog(@"%d", database.isOpened); // 输出 1
WCTDatabase* database2 = [[WCTDatabase alloc] initWithPath:filePath];
NSLog(@"%d", database2.isOpened); // 输出 1。WCDB 同一路径的数据库共享数据和状态等。
与打开数据库相对应,关闭数据库一般情况下也不需要开发者手动调用。当某个路径的WCTDatabase
已经完全dealloc时,数据库会自动关闭,并回收内存。
@autoreleasepool {
WCTDatabase* database1 = [[WCTDatabase alloc] initWithPath:filePath];
BOOL ret = [database1 createTable:@"sampleTable" withClass:Sample.class];// 数据库此时会被自动打开
NSLog(@"%d", database1.isOpened); // 输出 1
}// 作用域结束,database1 dealloc、关闭数据库并回收内存
WCTDatabase* database2 = [[WCTDatabase alloc] initWithPath:filePath];
NSLog(@"%d", database2.isOpened); // 输出 0。
WCTDatabase* database1 = [[WCTDatabase alloc] initWithPath:filePath];
@autoreleasepool {
WCTDatabase* database2 = [[WCTDatabase alloc] initWithPath:filePath];
BOOL ret = [database2 createTable:@"sampleTable" withClass:Sample.class];// 数据库此时会被自动打开
NSLog(@"%d", database2.isOpened); // 输出 1
}// 作用域结束,database2 dealloc,但 database1 仍持有该路径的数据库,因此不会被关闭。
NSLog(@"%d", database1.isOpened); // 输出 1。
同时,也可以调用 close
接口,手动关闭数据库。
WCTDatabase* database = [[WCTDatabase alloc] initWithPath:filePath];
NSLog(@"%d", database.canOpen); // 输出 1
NSLog(@"%d", database.isOpened); // 输出 1
[database close];
NSLog(@"%d", database.isOpened); // 输出 0
WCDB 也提供了
blockade
、unblockade
和isBlockaded
接口用于分步执行关闭数据库操作,可参考[相关接口文档][Swift-API-Reference-Close-OnClosed]
某些情况下,开发者需要确保数据库完全关闭后才能进行操作,如将数据库整体zip打包发送。
数据库是二进制文件,zip打包的过程中若数据发生了变化,则打包后的文件数据可能会不完整、损坏。因此,WCDB 提供了 close:
接口。
[database close:^{
//执行独占的数据库操作,如zip压缩打包
}];
在 close:
的回调范围 block 内,可确保数据库完全关闭,不会有其他线程的数据访问、操作数据库,因此可以安全地操作文件。
purge
接口用于回收暂不使用的内存,主要是关闭闲置的数据连接。
// 回收 database 数据库中暂不使用的内存
[database purge];
// 回收所有已创建的数据库中暂不使用的内存
[WCTDatabase purgeAll];
在 iOS 平台上,当内存不足、收到系统警告时,WCDB 会自动调用
[WCTDatabase purgeAll]
接口以减少内存占用。
// 获取所有与该数据库相关的文件路径
NSLog(@"%@", [database paths]);
// 获取所有与该数据库相关的文件占用的大小
[database close:^{
// 数据库未关闭状态下也可获取文件大小,但不够准确,开发者可自行选择是否关闭
NSLog(@"%lu", [database getFilesSize].value());
}];
// 删除所有与该数据库相关的文件
[database removeFiles];
// 将所有与该数据库相关的文件移动到另一个目录
[database moveFilesToDirectory:otherDirectory];
WCTTable
指代数据库中的一个表。可以通过 getTable:withClass:
接口获取。
//注意,即便表不实际存在也会返回,推荐在执行建表语句之后获取WCTTable
WCTTable* table = [database getTable:@"sampleTable" withClass:Sample.class];
WCTTable
相当于指定了表名和模型绑定类的 WCTDatabase
,其实只是 WCTDatabase
的简化版,增删查改中提到的所有接口WCTTable
都具备,而且这些接口调用时都不需要再传表名和class对象,下面是使用示例
// 返回值需指定为 [Sample] 类型以匹配范型
NSArray* allObjectsFromDatabase = [database getObjectsOfClass:Sample.class fromTable:@"sampleTable"];
// table 已经指定了表名和模型绑定的类,因此可以直接获取
NSArray* allObjectsFromTable = [table getObjects];
因为执行数据读写时WCTTable
使用起来比WCTDatabase
更加简洁,而且也有利于以表为单位来管理数据读写逻辑,所以我们推荐尽量使用WCTTable
来进行数据读写。
事务一般用于 提升性能 和 保证数据原子性。可以使用WCTDatabase
发起事务。
BOOL ret = [database runTransaction:^BOOL(WCTHandle *handle) {
BOOL ret = [table insertObject:object];
ret &= [table deleteObjectsWhere:Sample.identifier == 1];
return ret;
}];
事务回调中的WCTHandle
对象暂时忽略,后面在高级接口中会有介绍。
事务提升性能的实质是批量处理。
NSMutableArray* objects = [[NSMutableArray alloc] init];
for(int i = 0; i < 100000; i++) {
Sample* obj = [[Sample alloc] init];
obj.identifier = i;
[objects addObject:obj];
}
// 单独插入,效率很差
for(Sample* obj in objects) {
[table insertObject:obj];
}
// 事务插入,性能较好
[database runTransaction:^BOOL(WCTHandle * handle) {
for(Sample* obj in objects) {
[table insertObject:obj];
}
}];
// insertObjects: 接口内置了事务,并对批量数据做了针对性的优化,性能更好
[table insertObjects:objects];
试考虑以下代码:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[table deleteObjects];
});
[table insertObject:object];
NSArray* allObjects = [table getObjects];
NSLog(@"%lu", allObjects.count);// 可能输出 0 或 1
在多线程下,删除操作发生的时机是不确定的。倘若它发生在 插入完成之后 和 取出数据之前 的瞬间,则 getObjects
无法取出刚才插入的数据,且这种多线程低概率的 bug 是很难查的。
而事务可以保证一段操作的原子性:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[table deleteObjects];
});
[database runTransaction:^BOOL(WCTHandle *handle) {
[table insertObject:object];
NSArray* allObjects = [table getObjects];
NSLog(@"%lu", allObjects.count);// 输出 1
}];
WCDB 提供了三种事务,普通事务、嵌入事务和可中断事务。
// 普通事务
[database runTransaction:^BOOL(WCTHandle *handle) {
return [table insertObject:object];
}];
// 普通事务嵌套执行
[database runTransaction:^BOOL(WCTHandle *handle) {
[database runTransaction:^BOOL(WCTHandle *handle) {
return [table insertObject:object];
}];
}];
普通事务可以通过返回值控制提交或者回滚事务,返回YES提交事务,返回NO则回滚事务。
注意回滚事务时,事务调用本身的返回值也会为NO。
普通事务可以互相嵌套执行,嵌套时支持只回滚嵌套事务中的局部更改内容。
insertObjects:
、insertOrReplaceObjects:
、createTable:withClass:
等 WCDB 自带的接口都使用了嵌入事务
WCDB 也提供了
beginTransaction
、commitOrRollbackTransaction
和rollbackTransaction
接口用于分步执行事务,可参考相关接口文档
关于可中断事务,因为使用较为复杂,后面在高级接口中会有介绍。
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程