iOS学习47之第三方,数据库第三方框架FMDB详细讲

作者:新闻中心

iOS中原生的SQLite API在进行数据存储的时候,需要使用C语言中的函数,操作比较麻烦。于是,就出现了一系列将SQLite API进行封装的库,例如FMDBPlausibleDatabasesqlitepersistentobjects等。

iOS学习47之第三方-FMDB,ios47-fmdb

将 CocoaPods 安装后,按照 CocoaPods 的使用说明就可以将 FMDB 第三方集成到工程中,具体请看博客iOS学习46之第三方CocoaPods的安装和使用(通用方法)

FMDB是一款简洁、易用的封装库。因此,在这里推荐使用第三方框架FMDB,它是对libsqlite3框架的封装,用起来的步骤与SQLite使用类似,并且它对于多线程的并发操作进行了处理,所以是线程安全的。

1. FMDB简介

  • 优点:
    • 对多线程的并发操作进行处理,所以是线程安全的;
    • 以OC的方式封装了SQLite的C语言API,使用起来更加的方便;
    • FMDB是轻量级的框架,使用灵活。
  • 缺点:
    • 因为它是OC的语言封装的,只能在ios开发的时候使用,所以在实现跨平台操作的时候存在局限性。

 1> 概述

  • iOS 中原生的 SQLite API 在进行数据存储的时候,需要使用 C语言 中的函数,操作比较繁琐。于是,就出现了一系列将SQLite API 进行封装的库,例如 FMDB、PlausibleDatabase、SQLitePersistentObjects 等。  

  • FMDB 是一款简洁、易用的封装库。因此,在这里推荐使用第三方框架 FMDB,它是对 libsqlite3 框架的封装,用起来的步骤与 SQLite 使用类似,并且它对于多线程的并发操作进行了处理,所以是线程安全的。

 2> FMDB优缺点

  • 优点:

  对多线程的并发操作进行处理,所以是线程安全的;

  以OC的方式封装了SQLite的C语言API,使用起来更加的方便;

  FMDB是轻量级的框架,使用灵活。

  • 缺点:

  因为它是OC的语言封装的,只能在iOS开发的时候使用,所以在实现跨平台操作的时候存在局限性。

 3> FMDB中重要的类

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

  • FMResultSet:使用 FMDatabase 执行查询后的结果集。

  • FMDatabaseQueue:用于在多线程中执行多个查询或更新,它是线程安全的。

 4> FMDB使用步骤

  • 第一步:使用 CocoaPods 将第三方集成到工程项目中

  • 第二步:导入 libsqlite3.0 框架,导入头文件 FMDatabase.h

  • 第三步:代码实现,与 SQLite 使用步骤相似,创建数据库路径,获得数据库路径,打开数据库,然后对数据库进行增、删、改、查操作,最后关闭数据库。

  • FMDatabase
    • FMDatabase对象就代表一个单独的SQLite数据库,用来执行SQL语句
  • FMResultSet
    • 使用FMDatabase执行查询后的结果集
  • FMDatabaseQueue
    • 用于在多线程中执行多个查询或更新,它是线程安全的

2. FMDB创建数据库和数据表

  • 第一步:获得数据库文件的路径

  创建FMDatabase对象时参数为SQLite数据库文件路径,该路径可以是以下三种方式之一:

   ① 文件路径。该文件路径无需真实存在,如果不存在会自动创建;

   ② 空字符串(@"")。表示会在临时目录创建一个空的数据库,当 FMDatabase 连接关闭时,文件也会被删除;

   ③ NULL。将创建一个内在数据库,同样的,当 FMDatabase 连接关闭时,数据将会被销毁。

  我们一般采用第一种方式来获得数据库文件的路径,具体实例代码如下:

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

    self.filePath = [documentPath stringByAppendingPathComponent:@"student.sqlite"];

    NSLog(@"filePath = %@", self.filePath);
  • 第二步:使用路径初始化FMDB对象

  使用的初始化方法:

     (instancetype)databaseWithPath:(NSString*)aPath

  实例代码:

    // 第四步:使用路径初始化FMDB对象
    self.database = [FMDatabase databaseWithPath:self.filePath];
  • 第三步:在和数据库交互之前,数据库必须是打开的。

  如果权限不足或者资源不足,则无法打开和创建数据库。

   // 需要判断数据库打开的时候才进行执行语句
   if (self.database.open) {
        // 创建表
    }
  • 第四步:创建表

  使用的执行SQL语句的方法:

    - (BOOL)executeUpdate:(NSString*)sql, ...

  该方法的返回值是一个BOOL值,我们可以根据返回值,来判断SQL语句是否执行成功。

  实例代码:

    if (self.database.open) {
        // 建表语句
        NSString *createSql = @"create table if not exists t_student(id integer primary key autoincrement not null, name text not null, age integer not null, sax text not null)";
        // 执行建表语句,创建数据表
        BOOL result = [self.database executeUpdate:createSql];
        // 判断是否建表成功
        if (result) {
            NSLog(@"创表成功");
        } else {
            NSLog(@"创表失败");
        }
    }
  • 第五步:关闭数据库

  实例代码:

    // 第五步:关闭数据库
    [self.database close];

3. FMDB实现增、删、改、查

  • 下载FMDB文件GitHub,并将FMDB文件夹添加到项目中(也可使用CocoaPods导入)
  • 导入libsqlite3.0框架,导入头文件FMDatabase.h
  • 代码实现,与SQLite使用步骤相似,创建数据库路径,获得数据库路径,打开数据库,然后对数据库进行增、删、改、查操作,最后关闭数据库。

 1> FMDB—执行更新

  一切不是SELECT命令的命令都视为更新。这包括CREAT,UPDATE,INSERT,ALTER,BEGIN,COMMIT,DETACH,DELETE,DROP,END,EXPLAIN,VACUUM,REPLACE等。  

  简单来说,只要不是以 SELECT 开头的命令都是更新命令。

      执行更新返回一个BOOL值。YES表示执行成功,否则表示有错误。你可以调用 -lastErrorMessage 和 -lastErrorCode方法来得到更多信息。

图片 1step.png

 2> 执行更新命令的相关方法

  • executeUpdate:  不确定的参数用 ? 来占位(后面参数必须是 OC 对象,;代表语句结束,也可以不写)

  增、删、改的代码实例:

// 增加(插入)数据
BOOL result = [self.database executeUpdate:@"insert into t_student(name, age, sax) values(?, ?, ?)", @"xiaoming", @12, @"男"];

// 更新数据
BOOL result = [self.database executeUpdate:@"update t_student set name = ? where name = ?", @"xiaoming", @"小明"];

// 删除数据
BOOL result = [self.database executeUpdate:@"delete from t_student where name = ?", @"xiaoming"]; 
  • executeUpdateWithFormat:不确定的参数用%@,%d等来占位 (参数为原始数据类型,执行语句不区分大小写)

  增、删、改的代码实例:

// 增加(插入)数据
BOOL result = [self.database executeUpdateWithFormat:@"insert into t_student (name, age, sax) values (%@, %i, %@);", @"xiaoming", @69, @"男"];

// 更新数据
BOOL result = [self.database executeUpdateWithFormat:@"update t_student set name = %@ where name = %@", @"xiaoming", @"小明"];

// 删除数据
BOOL result = [self.database executeUpdateWithFormat:@"delete from t_student where name = %@", @"xiaoming"];
  • executeUpdate:withArgumentsInArray:数组,直接使用数组

  增、删、改的代码实例:

// 增加(插入)数据
[self.database executeUpdate:@"insert into t_student(name, age, sax) values(?, ?, ?);"  withArgumentsInArray:@[@"xiaoming", @12, @"男"]];

// 更新数据
[self.database executeUpdate:@"update t_student set name = ? where name = ?;"  withArgumentsInArray:@[@"xiaoming", @"小明"]];

// 删除数据
[self.database executeUpdate:@"delete from t_student where name = ?;"  withArgumentsInArray:@[@"xiaoming"]];

  以上的方法大家可以根据自己的习惯和需求选择一种即可。

数据库创建

创建FMDatabase对象时参数为SQLite数据库文件路径,该路径可以是以下三种方式之一

  • 文件路径。该文件路径无需真实存在,如果不存在会自动创建

  • 空字符串。表示会在临时目录创建一个空的数据库,当FMDatabase连接关闭时,文件也会被删除

  • NULL。将创建一个内在数据库,同样的,当FMDatabase连接关闭时,数据将会被销毁

  • 本文中使用的测试模型类.h

图片 2student.png

 3> FMDB—查询数据

数据库使用FMDB框架代码操作
  • 使用FMDataBase类建立数据库

    //1.获得数据库文件的路径NSString *doc =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) lastObject]; NSString *fileName = [doc stringByAppendingPathComponent:@“student.sqlite”]; //2.获得数据库FMDatabase *db = [FMDatabase databaseWithPath:fileName];//3.使用如下语句,如果打开失败,可能是权限不足或者资源不足。通常打开完操作操作后,需要调用 close 方法来关闭数据库。在和数据库交互 之前,数据库必须是打开的。如果资源或权限不足无法打开或创建数据库,都会导致打开失败。if ([db open]){ //4.创表 BOOL result = [db executeUpdate:@“CREATE TABLE IF NOT EXISTS t_student (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);”]; if  { NSLog; }}
    
  • 查看sql表

  • 根据路径fileName在Finder中搜索.sqlite文件,并复制到桌面

  • 使用火狐浏览器工具下的SQLite Manager打开.sqlite文件

    图片 32.png

  • 数据表结构

图片 4sql.png

  ① 概述  

  SELECT 命令就是查询,执行查询的方法是以 -excuteQuery 开头的。

  执行查询时,如果成功返回 FMResultSet 对象,错误返回 nil 。与执行更新相同,支持使用 NSError 参数。

  同时,你也可以使用 -lastErrorCode 和 -lastErrorMessage 获知错误信息。

使用FMDataBase类执行数据库命令SQL

一切不是SELECT命令的命令都视为更新。这包括 CREAT,UPDATE,INSERT,ALTER,BEGIN,COMMIT,DETACH,DELETE,DROP,END,EXPLAIN,VACUUM,REPLACE等。

简单来说,只要不是以SELECT开头的命令都是更新命令。

执行更新返回一个BOOL值。YES表示 执行成功,否则表示有错误。你可以调用 -lastErrorMessage 和 -lastErrorCode方法来得到更多信息。

  • 使用FMDataBase类执行数据库插入命令SQLinsert into

    int age = 42;//1.executeUpdate:不确定的参数用?来占位(后面参数必须是oc对象,;代表语句结束)[self.db executeUpdate:@“INSERT INTO t_student (name, age) VALUES ;”,name,@];//2.executeUpdateWithForamat:不确定的参数用%@,%d等来占位 (参数为原始数据类型,执行语句不区分大小写)[self.db executeUpdateWithForamat:@“insert into t_student  values ;”,name,age]; //3.参数是数组的使用方式[self.db executeUpdate:@“INSERT INTO t_student VALUES ;”withArgumentsInArray:@[name,@]];
    
  • 使用FMDataBase类执行数据库删除命令SQLdelete

    //1.不确定的参数用?来占位 (后面参数必须是oc对象,需要将int包装成OC对象)int idNum = 101;[self.db executeUpdate:@“delete from t_student where id = ?;”,@];//2.不确定的参数用%@,%d等来占位[self.db executeUpdateWithFormat:@“delete from t_student where name = %@;”,@“apple_name”];
    
  • 使用FMDataBase类执行数据库修改命令SQLupdate

    //修改学生的名字[self.db executeUpdate:@“update t_student set name = ? where name = ?”,newName,oldName];
    
  • 使用FMDataBase类执行数据库查询命令SQLselect ... from

    • SELECT命令就是查询,执行查询的方法是以-excuteQuery开头的。
    • 执行查询时,如果成功返回FMResultSet对象,错误返回nil。与执行更新相当,支持使用NSError参数。
    • 同时,你也可以使用-lastErrorCode和-lastErrorMessage获知错误信息。
  • FMResultSet获取不同数据格式的方法

  • intForColumn:

  • longForColumn:

  • longLongIntForColumn:

  • boolForColumn:

  • doubleForColumn:

  • stringForColumn:

  • dataForColumn:

  • dataNoCopyForColumn:

  • UTF8StringForColumnIndex:

  • objectForColumn:

  • 使用FMResultSet获取查询语句结果

     //查询整个表FMResultSet *resultSet = [self.db execute Query:@“select * from t_student;”]; //根据条件查询FMResultSet *resultSet = [self.db executeQuery:@“select * from t_student where id<?;”@]; //遍历结果集合 while ([resultSet next]) { int idNum = [resultSet intForColumn:@“id”]; NSString *name = [resultSet objectForColumn:@“name”]; int age = [resultSet intForColumn:@“age”]; } 
    
  • 使用FMDataBase类执行数据库销毁命令SQLdrop ...

    //如果表格存在 则销毁[self.db executeUpadate:@“drop table if exists t_student;”];
    
  • 使用FMDatabaseQueue类实现多线程操作

在多个线程中同时使用一个FMDatabase实例是不明智的。现在你可以为每 个线程创建一个FMDatabase对象,不要让多个线程分享同一个实例,他无 法在多个线程中同事使用。否则程序会时不时崩溃或者报告异常。所以,不要 初始化FMDatabase对象,然后在多个线程中使用。这时候,我们就需要使 用FMDatabaseQueue来创建队列执行事务。

 //1.创建队列 FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath]; __block BOOL whoopsSomethingWrongHappened = true; //2.把任务包装到事务里 [queue inTransaction:^(FMDatabase *db, BOOL *rollback) { whoopsSomethingWrongHappened &= [db executeUpdate:@“INSERT INTO myTable VALUES ”, [NSNumber numberWith:1]];whoopsSomethingWrongHappened &= [dbexecuteUpdata:@“INSERT INTO myTable VALUES ”, [NSNumber numberWithInt:2]];whoopsSomethingWrongHappened &= [db executeUpdata:@“INSERT INTO myTable VALUES ”[NSNumber numberWithInt:3]];//如果有错误 返回if (!whoopsSomethingWrongHappened) { *rollback = YES; return; }}];

好了,到此为止,相信你已经能够使用FMDB进行数据持久化了,它的好与坏 只有在不断地使用过程中才能发现了解。所以,希望大家学会了以后还是要多 写多练多使用。另外,诚心希望大家多提宝贵意见,或者沟通一些好的想法。^^

  ② FMResultSet

  FMResultSet 提供了很多方法,来获取对应字段的信息:  

    intForColumn:、longForColumn:、longLongIntForColumn:、boolForColumn:、doubleForColumn:、stringForColumn:、dataForColumn:、dataNoCopyForColumn:、UTF8StringForColumnIndex:、objectForColumn:

  ③ 执行查询语句

  查询整个表

    // 查询结果使用的类FMResultSet
    FMResultSet *resultSet = [self.database executeQuery:@"select * from t_student"];

  根据条件查询

    //根据条件查询
    FMResultSet *resultSet = [self.db executeQuery:@"select * from t_student where id<?;", @14];

  ④ 遍历结果集合

    while (resultSet.next) {
        NSInteger ID = [resultSet intForColumn:@"id"];
        NSString *name = [resultSet objectForColumnName:@"name"];
        NSInteger age = [resultSet intForColumn:@"age"];
        NSString *sax = [resultSet objectForColumnName:@"sax"];

        NSLog(@"id = %ld name = %@, age = %ld, sax = %@", ID, name, age, sax);
    }

 4> 完整实例代码

#import "ViewController.h"

// 第一步:引入框架,引入支持的类库(libsqlite3.0.tbd)
#import <FMDB.h>

@interface ViewController ()

/// 声明数据库对象
@property (nonatomic, strong) FMDatabase *database;

/// 声明存储路径
@property (nonatomic, strong) NSString *filePath;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self createTabe];
}

#pragma mark - 创建表
- (void)createTabe
{
    // 第一步:创建sql语句
    NSString *createSql = @"create table if not exists t_student(id integer primary key autoincrement not null, name text not null, age integer not null, sax text not null)";

    // 第二步:找到存储路径
    NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//    NSLog(@"document = %@", documentPath);
    self.filePath = [documentPath stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"filePath = %@", self.filePath);

    // 第三步:使用路径初始化FMDB对象
    self.database = [FMDatabase databaseWithPath:self.filePath];

    // 第四步:数据库执行相关的操作
    // 需要判断数据库打开的时候才进行执行语句
    if (self.database.open) {
        BOOL result = [self.database executeUpdate:createSql];
        if (result) {
            NSLog(@"创表成功");
        } else {
            NSLog(@"创表失败");
        }
    }

    // 第五步:关闭数据库
    [self.database close];
}

#pragma mark - 插入
- (IBAction)insertIntoAction:(id)sender
{
    // 第一步:打开数据库
    [self.database open];

    // 第二步:进行相关的操作
    NSArray *nameArray = @[@"MBBoy", @"BoomSky", @"小明"];

    for (NSString *name in nameArray) {

        BOOL result = [self.database executeUpdate:@"insert into t_student(name, age, sax) values(?, ?, ?)", name, @69, @"男"]; // integer的数据不能在这里使用,必须使用一个对象型数据,比如NSNumber、NSString...

        [self.database executeUpdate:@"INSERT INTO t_student(name, age, sax) VALUES  (?, ?, ?);"  withArgumentsInArray:@[@"xiaoming", @12, @"男"]];

        if (result) {
            NSLog(@"插入成功");
        } else {
            NSLog(@"插入失败");
        }
    }
    [self.database close];// 更新数据// 删除数据// 增加(插入)数据
}

#pragma mark - 更新
- (IBAction)updateAction:(id)sender
{
    [self.database open];

    BOOL result = [self.database executeUpdate:@"update t_student set name = ? where name = ?", @"xiaoming", @"小明"];

    if (result) {
        NSLog(@"更新成功");
    } else {
        NSLog(@"更新失败");
    }

    [self.database close];
}

#pragma mark - 删除
- (IBAction)deleteAction:(id)sender
{
    [self.database open];
    BOOL result = [self.database executeUpdate:@"delete from t_student where name = ?", @"MBBoy"];
    if (result) {
        NSLog(@"删除成功");
    } else {
        NSLog(@"删除失败");
    }
    [self.database close];
}

#pragma mark - 查询
- (IBAction)selectAction:(id)sender
{
    [self.database open];

    // 查询结果使用的类FMResultSet
    FMResultSet *resultSet = [self.database executeQuery:@"select * from t_student"];

    // 遍历出需要的结果内容
    while (resultSet.next) {
        NSInteger ID = [resultSet intForColumn:@"id"];
        NSString *name = [resultSet objectForColumnName:@"name"];
        NSInteger age = [resultSet intForColumn:@"age"];
        NSString *sax = [resultSet objectForColumnName:@"sax"];

        NSLog(@"id = %ld name = %@, age = %ld, sax = %@", ID, name, age, sax);
    }

    [self.database close];
}

@end

4. FMDB实现多线程操作

 1> 概述

  • 如果应用中使用了多线程操作数据库,那么就需要使用 FMDatabaseQueue 来保证线程安全了。 应用中不可在多个线程中共同使用一个 FMDatabase 对象操作数据库,这样会引起数据库数据混乱(例如使用两个线程同时对数据库进行更新和查找)。
  • 多个线程更新相同的资源导致数据竞争时使用等待队列(等待现在执行的处理结束)。

  • 以队列的形式添加是 FMDB 比较常用的添加方式。

  • FMDB 不支持多个线程同时操作,一般使用串行方式实现相关的操作。

 2> 创建操作队列

  使用的初始化方法

  (instancetype)databaseQueueWithPath:(NSString*)aPath

 3> 把操作打包放在操作队列中

  打包的方法

- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block

  在 Block 中添加串行队列

 4> 实例代码

#pragma mark - 以队列的形式添加很多学生
- (IBAction)insertManyStudent:(id)sender
{
    // 以队列的形式添加学生是FMDB比较常用的添加方式
    // FMDB不支持多个线程同时操作,一般使用串行方式实现相关的操作

    [self.database open];

    // 第一步:创建操作队列
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:self.filePath];

    // 标识:记录是否操作成功
    __block BOOL isSucceed = YES;

    // 第二步:把操作打包放在操作队列中
    NSString *insertSql = @"insert into t_studen(name, age, sax) values(?, ?, ?)";

    [queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        // 串行队列
        isSucceed &= [db executeUpdate:insertSql, @"隔壁老王", @38, @"男"];
        isSucceed &= [db executeUpdate:insertSql, @"Black", @18, @"女"];
        isSucceed &= [db executeUpdate:insertSql, @"-1", @23, @"男"];

        if (!isSucceed) {

            // block 返回的参数rollback进行处理(BOOL类型的指针)
            *rollback = YES;
            return;
        } else {
            NSLog(@"以队列的形式添加成功");
        }
    }];

    [self.database close];
}

 

将 CocoaPods 安装后,按照 CocoaPods 的使用说明就可以将 FMDB 第三方集成到工程中,具体请看博客iOS学习46之第...

本文由新葡京8455发布,转载请注明来源

关键词: