当前位置: 首页 > 工具软件 > FMDB > 使用案例 >

iOS - 数据持久化之 FMDB 的使用

羊舌昆杰
2023-12-01

前言

上一篇文章「iOS - 使用 SQLite 数据库实现数据持久化」,介绍了如何使用 sqlite3 操作 SQLite 数据库实现增删改查。但是在代码编写的过程中,我们发现 sqlite3 需要调用大量的 C 语言函数,需要进行各种 C 语言类型到 OC 对象的转换,这会带来很多使用上的不便。

于是,经过查找发现了基于 sqlite3 封装的 FMDB,它能够较大程度地简化我们的代码,也就是本文将要介绍的内容。

FMDB 简介

FMDB 是 iOS 平台的 SQLite 数据库开源库,它对 libsqlite3 的 C 语言 API 用 OC 的方法进行了封装,它的使用和 libsqlite3 相似,但是大大提高了使用的便捷性。同时,还提供了多线程安全的数据库操作方法,能有效地防止数据混乱。

Github 仓库地址:https://github.com/ccgus/fmdb

FMDB 三大核心类

  • FMDatabase 表示数据库类,一个 FMDatabase 对象就代表一个单独的 SQLite 数据库用来执行 SQL 语句。

  • FMResultSet 表示查询结果集类,用于接收 FMDatabase 执行查询后的结果集。

  • FMDatabaseQueue 封装了线程安全数据库操作,用于在多线程中执行多个查询或更新,它是线程安全的。

FMDB 的引入

因为 FMDB 是依赖于 sqlite3 的,所以先要在项目中导入 libsqlite3.tbd,详情可以参考上一篇文章「iOS - 使用 SQLite 数据库实现数据持久化」。

现在的项目大多都是通过 Cocoapods 管理依赖的库,我们只需要在 Podfile 中添加如下依赖,然后执行 pod Install 就能引入 FMDB 了。

pod 'FMDB'

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];
}

查询操作

执行查询操作的步骤:

  • 使用 FMDatabase 对象的 open 方法打开数据库
  • 使用 FMDatabase 对象的 executeQuery: 方法执行查询语句
  • 使用 FMResultSet 对象接收查询得到的结果集
  • 通过 FMResultSet 对象的 xxxForColumn: 系列方法获取到查询的数据
  • 关闭 FMDatabase 和 FMResultSet 对象
- (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 线程安全

在多线程的场景下,我们通常会有保证读写操作线程安全的需求,这时我们可以用上 FMDB 提供的 FMDatabaseQueue 类,在其对象中执行的读写任务都是线程安全的。下面简单介绍它的使用方法:

创建 FMDatabaseQueue 对象,需要传入数据库的存储路径。

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];

FMDatabaseQueue 类中内置了 FMDatabase 数据库对象,不需要我们再额外进行数据库的创建。我们只需要通过 Block 的形式给 FMDatabaseQueue 对象传入任务,在 Block 执行需要的 SQL 语句即可。

[queue inDatabase:^(FMDatabase * _Nonnull db) {
	// 调用 db 的相应方法执行 SQL 语句
}];

参考资料

 类似资料: