上一篇文章「iOS - 使用 SQLite 数据库实现数据持久化」,介绍了如何使用 sqlite3 操作 SQLite 数据库实现增删改查。但是在代码编写的过程中,我们发现 sqlite3 需要调用大量的 C 语言函数,需要进行各种 C 语言类型到 OC 对象的转换,这会带来很多使用上的不便。
于是,经过查找发现了基于 sqlite3 封装的 FMDB,它能够较大程度地简化我们的代码,也就是本文将要介绍的内容。
FMDB 是 iOS 平台的 SQLite 数据库开源库,它对 libsqlite3 的 C 语言 API 用 OC 的方法进行了封装,它的使用和 libsqlite3 相似,但是大大提高了使用的便捷性。同时,还提供了多线程安全的数据库操作方法,能有效地防止数据混乱。
Github 仓库地址:https://github.com/ccgus/fmdb
FMDB 三大核心类:
FMDatabase
表示数据库类,一个 FMDatabase 对象就代表一个单独的 SQLite 数据库用来执行 SQL 语句。
FMResultSet
表示查询结果集类,用于接收 FMDatabase 执行查询后的结果集。
FMDatabaseQueue
封装了线程安全数据库操作,用于在多线程中执行多个查询或更新,它是线程安全的。
因为 FMDB 是依赖于 sqlite3 的,所以先要在项目中导入 libsqlite3.tbd,详情可以参考上一篇文章「iOS - 使用 SQLite 数据库实现数据持久化」。
现在的项目大多都是通过 Cocoapods 管理依赖的库,我们只需要在 Podfile 中添加如下依赖,然后执行 pod Install
就能引入 FMDB 了。
pod 'FMDB'
为了能充分感受到 FMDB 的便捷,我们依旧使用上一篇文章提到的例子:「我们有个用户表,里面有用户id,用户名称和用户年龄等三个字段,我们使用 FMDB 对它进行增删改查」。
我们还是创建一个 VGFMUserDAO
类来完成上述数组操作,该类提供的 API 接口还是和上一篇文章保持一致。
#import <fmdb/FMDB.h>
@interface VGFMUserDAO ()
// FMDatabase对象,表示一个数据库
@property (nonatomic, strong) FMDatabase *db;
@end
单例对象的创建方法:
+ (instancetype)sheredInstance {
static VGFMUserDAO *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[VGFMUserDAO alloc] init];
// 获取数据库的存储路径,该方法的实现和上一篇文章保持一致
NSString *dbFilePath = [sharedInstance applicationDocumentsDirectory];
// 初始化单例对象的数据库属性
sharedInstance.db = [FMDatabase databaseWithPath:dbFilePath];
// 执行建表操作
[sharedInstance createEditableDatabaseIfNeed];
});
return sharedInstance;
}
这里使用到了 FMDatabase 对象的 executeUpdate:
方法执行建表语句。在 FMDB 中,除了查询操作之外,执行其他 SQL 语句使用的都是 executeUpdate:
方法。
- (void)createEditableDatabaseIfNeed {
if (![self.db open]) {
NSLog(@"数据库打开失败");
} else {
// 执行建表语句
BOOL result = [self.db executeUpdate:@"create table if not exists user (user_id text primary key, user_name text, user_age integer)"];
if (!result) {
NSLog(@"建表失败");
}
}
[self.db close];
}
执行查询操作的步骤:
open
方法打开数据库executeQuery:
方法执行查询语句xxxForColumn:
系列方法获取到查询的数据- (VGUser *)findById:(NSString *)userId {
if (![self.db open]) {
NSLog(@"数据库打开失败");
} else {
// 获取查询的结果集
FMResultSet *rs = [self.db executeQuery:@"select user_id, user_name, user_age from user where user_id = ?", userId];
// 判断是否有查询结果
if ([rs next]) {
VGUser *user = [[VGUser alloc] initWithUserId:[rs stringForColumn:@"user_id"]
userName:[rs stringForColumn:@"user_name"]
userAge:[rs intForColumn:@"user_age"]];
[rs close];
[self.db close];
return user;
}
[rs close];
}
[self.db close];
return nil;
}
这个是查询用户表所有数据的方法,可以看到当 [rs next]
的返回值为真时,
- (NSArray *)findAll {
if (![self.db open]) {
NSLog(@"数据库打开失败");
} else {
FMResultSet *rs = [self.db executeQuery:@"select user_id, user_name, user_age from user"];
NSMutableArray *userList = [NSMutableArray array];
// 当条件成立,
while ([rs next]) {
VGUser *user = [[VGUser alloc] initWithUserId:[rs stringForColumn:@"user_id"]
userName:[rs stringForColumn:@"user_name"]
userAge:[rs intForColumn:@"user_age"]];
[userList addObject:user];
}
[rs close];
[self.db close];
return userList;
}
[self.db close];
return nil;
}
本操作使用到了 FMDatabase 对象的 executeUpdate:
方法执行 SQL 插入语句,需要注意的是,在传入参数的时候要把基本类型转化为 OC 对象。
- (void)create:(VGUser *)user {
if (![self.db open]) {
NSLog(@"数据库打开失败");
} else {
// 这里注意要把 user.userAge 转化为
BOOL result = [self.db executeUpdate:@"insert or replace into user (user_id, user_name, user_age) values (?, ?, ?)", user.userId, user.userName, @(user.userAge)];
if (!result) {
NSLog(@"数据插入失败");
}
}
[self.db close];
}
- (void)remove:(NSString *)userId {
if (![self.db open]) {
NSLog(@"数据库打开失败");
} else {
BOOL result = [self.db executeUpdate:@"delete from user where user_id = ?", userId];
if (!result) {
NSLog(@"数据删除失败");
}
}
[self.db close];
}
- (void)modify:(VGUser *)user {
if (![self.db open]) {
NSLog(@"数据库打开失败");
} else {
BOOL result = [self.db executeUpdate:@"update user set user_name = ?, user_age = ? where user_id = ?", user.userName, @(user.userAge), user.userId];
if (!result) {
NSLog(@"数据删除失败");
}
}
[self.db close];
}
整段代码写下来,可以发现 FMDB 使用起来非常方便,减少了很多 C 语言函数的调用步骤,让编写的代码变得简洁,代码行数减少了一半左右。
在多线程的场景下,我们通常会有保证读写操作线程安全的需求,这时我们可以用上 FMDB 提供的 FMDatabaseQueue
类,在其对象中执行的读写任务都是线程安全的。下面简单介绍它的使用方法:
创建 FMDatabaseQueue
对象,需要传入数据库的存储路径。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
FMDatabaseQueue
类中内置了 FMDatabase
数据库对象,不需要我们再额外进行数据库的创建。我们只需要通过 Block 的形式给 FMDatabaseQueue
对象传入任务,在 Block 执行需要的 SQL 语句即可。
[queue inDatabase:^(FMDatabase * _Nonnull db) {
// 调用 db 的相应方法执行 SQL 语句
}];