前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[iOS学习笔记]·FMDB:第三方本地数据库处理框架(官方文档翻译篇)

[iOS学习笔记]·FMDB:第三方本地数据库处理框架(官方文档翻译篇)

作者头像
陈满iOS
发布2018-09-10 11:11:31
1.2K0
发布2018-09-10 11:11:31
举报
文章被收录于专栏:陈满iOS陈满iOS

目前,虽然SQLite也为iOS提供了数据库操作方法,但更多的时候,一般用FMDB,正如主流APP(如QQ和微信)会用到。这里介绍一个查询主流APP主要框架的网站:AppSight

1.使用方法(Usage)


FMDB有三个主要的类:

  • FMDatabase:表示一个单独的SQLite数据库。 用来执行SQLite的命令。
  • FMResultSet:表示FMDatabase执行查询后结果集
  • FMDatabaseQueue:如果你想在多线程中执行多个查询或更新,你应该使用该类。这是线程安全的。

1.1 数据库创建(Database Creation)

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

  • 1.文件路径。该文件路径无需真实存,如果不存在会自动创建。
  • 2.空字符串(@"")。表示会在临时目录创建一个空的数据库,当FMDatabase 链接关闭时,文件也被删除。
  • 3.NULL. 将创建一个内在数据库。同样的,当FMDatabase连接关闭时,数据会被销毁。
代码语言:javascript
复制
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];

(如需对临时数据库或内在数据库进行一步了解,请继续阅读:http://www.sqlite.org/inmemorydb.html)

1.2 打开数据库(Opening)

在和数据库交互之前,数据库必须是打开的。如果资源或权限不足无法打开或创建数据库,都会导致打开失败。

代码语言:javascript
复制
if (![db open]) {    
        [db release];   //ARC无需此行
        return;    
    }  

1.3 执行更新(Executing Updates)

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

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

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

执行更新的方法是以-executeUpdate:开头的。

1.4 执行查询(Executing Queries)

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

执行查询时,如果成功返回FMResultSet对象,错误返回nil. 与执行更新相当,支持使用 NSError**参数。同时,你也可以使用-lastErrorCode-lastErrorMessage获知错误信息。

为了遍历查询结果,你可以使用while循环。你还需要知道怎么跳到下一个记录。使用FMDB,很简单实现,就像这样:

代码语言:javascript
复制
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
    //retrieve values for each record
}

在你访问查询返回值之前,你必须一直调用-[FMResultSet next] ,即使你只想要一个记录:

代码语言:javascript
复制
FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"];
if ([s next]) {
    int totalCount = [s intForColumnIndex:0];
}

FMResultSet 提供了很多方法来获得所需的格式的值:

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

这些方法也都包括 {type}ForColumnIndex的这样子的方法,参数是查询结果集的列的索引位置。

你无需调用 [FMResultSet close]来关闭结果集, 当新的结果集产生,或者其数据库关闭时,会自动关闭。

1.5 关闭数据库(Closing)

当使用完数据库,你应该-close 来关闭数据库连接来释放SQLite使用的资源。

代码语言:javascript
复制
[db close];  

1.6 事务(Transactions)

FMDatabase是支持事务的。

1.7 多重语句和批次信息(Multiple Statements and Batch Stuff)

您可以使用FMDatabaseexecuteStatements:withResultBlock:在字符串中执行多个语句:

代码语言:javascript
复制
NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
                 "create table bulktest2 (id integer primary key autoincrement, y text);"
                 "create table bulktest3 (id integer primary key autoincrement, z text);"
                 "insert into bulktest1 (x) values ('XXX');"
                 "insert into bulktest2 (y) values ('YYY');"
                 "insert into bulktest3 (z) values ('ZZZ');";

success = [db executeStatements:sql];

sql = @"select count(*) as count from bulktest1;"
       "select count(*) as count from bulktest2;"
       "select count(*) as count from bulktest3;";

success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
    NSInteger count = [dictionary[@"count"] integerValue];
    XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
    return 0;
}];

1.8 数据格式化(Data Sanitization)

利用一个SQL语句为FMDB插入数据前,你不要尝试SQL审查(sanitize)任何值。相反的,你应该使用标准的SQLite数据绑定语法。

代码语言:javascript
复制
INSERT INTO myTable VALUES (?, ?, ?, ?) 

?字符由SQLite识别为要插入的值的占位符。这些执行方法全部接受数量可变的参数(或这些参数的一个代表,例如NSArray,NSDictionaryva_list)。

并且,在Objective-C中将该SQL的占位符?一起使用:

代码语言:javascript
复制
NSInteger identifier = 42;
NSString *name = @"Liam O'Flaherty (\"the famous Irish author\")";
NSDate *date = [NSDate date];
NSString *comment = nil;

BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", @(identifier), name, date, comment ?: [NSNull null]];
if (!success) {
    NSLog(@"error = %@", [db lastErrorMessage]);
}

注意:基本数据类型,如NSInteger变量identifier,应该是一个NSNumber对象,通过使用@如上所示的语法实现。或者您也可以使用NSNumber numberWithInt:identifier语法。 同样,NULL应该插入SQL 值NSNull null。例如,在案件的comment,这可能是nil(而且是在这个例子中),你可以使用comment ?: NSNull null语法,如果将插入字符串comment不是nil,而是将插入NSNull null如果它是nil。

在Swift中,您将使用它executeUpdate(values:),这不仅仅是一个简洁的Swift语法,而且也是throws错误处理正确的错误:

代码语言:javascript
复制
do {
    let identifier = 42
    let name = "Liam O'Flaherty (\"the famous Irish author\")"
    let date = Date()
    let comment: String? = nil

    try db.executeUpdate("INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", values: [identifier, name, date, comment ?? NSNull()])
} catch {
    print("error = \(error)")
}

注意:在Swift中,您不必像Objective-C那样包装基本的数字类型。但是如果要插入一个可选的字符串,你可能会使用comment ?? NSNull()语法(即,如果是nil,使用NSNull,否则使用字符串)。

或者,您可以使用命名参数语法:

代码语言:javascript
复制
INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)

参数名必须以冒名开头。SQLite本身支持其他字符,但Dictionary key的内部实现是冒号开头,所以注意你的NSDictionary key不要包含冒号。

代码语言:javascript
复制
NSDictionary *arguments = @{@"identifier": @(identifier), @"name": name, @"date": date, @"comment": comment ?: [NSNull null]};
BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)" withParameterDictionary:arguments];
if (!success) {
    NSLog(@"error = %@", [db lastErrorMessage]);
}

关键是不要使用NSString方法stringWithFormat手动将值插入SQL语句本身。一个Swift字符串插入也不应该将值插入到SQL中。使用?占位符将值插入到数据库中(或WHERE在SELECT语句中的子句中使用)。

1.9 补充:老版本的README

提供给-executeUpdate:方法的参数都必须是对象。就像以下的代码就无法工作,且会产生崩溃。

代码语言:javascript
复制
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", 42];   

正确有做法是把数字打包成 NSNumber对象

代码语言:javascript
复制
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:42]];   

或者,你可以使用 -execute*WithFormat: ,这是NSString风格的参数

代码语言:javascript
复制
[db executeUpdateWithFormat:@"INSERT INTO myTable VALUES (%d)", 42];   

-execute*WithFormat: 的方法的内部实现会帮你封装数据, 以下这些修饰符都可以使用: %@, %c, %s, %d, %D,%i, %u, %U, %hi, %hu, %qi, %qu, %f, %g, %ld, %lu, %lld, and %llu。 除此之外的修饰符可能导致无法预知的结果。 一些情况下,你如果要在SQL语句中使用 % 字符,你应该使用%%

2. 使用FMDatabaseQueue 及线程安全 (Using FMDatabaseQueue and Thread Safety)


在多个线程中同时使用一个FMDatabase实例是不明智的。一个线程一个FMDatabase对象一直是可以的。只是不要跨线程共享单个实例,绝对不要同时跨多个线程。否则,意外会经常发生,程序会时不时崩溃,或者报告异常。总之很崩溃。

所以,不要实例化单个FMDatabase对象,并在多个线程中使用。

而是使用FMDatabaseQueue。实例化一个FMDatabaseQueue,并跨多个线程使用它。该FMDatabaseQueue对象将同步并协调跨多个线程的访问。以下是如何使用它:

首先,让你的队列。

代码语言:javascript
复制
FMDatabaseQueue * queue = [FMDatabaseQueue databaseQueueWithPath: aPath];

然后使用它像这样:

代码语言:javascript
复制
[queue inDatabase: ^(FMDatabase * db){ 
    [db executeUpdate:@“ INSERT INTO myTable VALUES(?)”,@ 1 ]; 
    [db executeUpdate:@“ INSERT INTO myTable VALUES(?)”,@ 2 ]; 
    [db executeUpdate:@“ INSERT INTO myTable VALUES(?)”,@ 3 ]; 

    FMResultSet * rs = [db executeQuery:@“ select * from foo ” ];
    while([rs next ]){ 
        ... 
    } 
}]

在transaction中封装事务的简单方法:

代码语言:javascript
复制
[queue inTransaction: ^(FMDatabase * db,BOOL * rollback){ 
    [db executeUpdate:@“ INSERT INTO myTable VALUES(?)”,@ 1 ]; 
    [db executeUpdate:@“ INSERT INTO myTable VALUES(?)”,@ 2 ]; 
    [db executeUpdate:@“ INSERT INTO myTable VALUES(?)”,@ 3 ]; if(whoopsSomethingWrongHappened){ 
        * rollback = YES ;
        return ; 
    } // etc ... 
}];

Swift相应的版本为:

代码语言:javascript
复制
queue.inTransaction { db, rollback in
    do {
        try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [1])
        try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [2])
        try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [3])

        if whoopsSomethingWrongHappened {
            rollback.pointee = true
            return
        }

        // etc ...
    } catch {
        rollback.pointee = true
        print(error)
    }
}

(注意,从Swift 3开始使用pointee,但在Swift 2.3中,使用memory而不是pointee。)

FMDatabaseQueue将运行(序列化队列上的)块(因此是类名)。所以如果你同时从多个线程调用FMDatabaseQueue的方法,它们将按照它们被接收的顺序执行。这样查询和更新将不会对对方的脚趾,每一个都很开心。

注意:对FMDatabaseQueue方法的调用是阻塞的。所以即使你正在传递块,它们也不会在另一个线程上运行。

3. 基于块制作定制的sqlite函数(Making custom sqlite functions, based on blocks)


你可以这样做!例如,-makeFunctionNamed:在main.m中查找

英文原文出处:

https://github.com/ccgus/fmdb

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.07.12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.使用方法(Usage)
    • 1.1 数据库创建(Database Creation)
      • 1.2 打开数据库(Opening)
        • 1.3 执行更新(Executing Updates)
          • 1.4 执行查询(Executing Queries)
            • 1.5 关闭数据库(Closing)
              • 1.6 事务(Transactions)
                • 1.7 多重语句和批次信息(Multiple Statements and Batch Stuff)
                  • 1.8 数据格式化(Data Sanitization)
                    • 1.9 补充:老版本的README
                    • 2. 使用FMDatabaseQueue 及线程安全 (Using FMDatabaseQueue and Thread Safety)
                    • 3. 基于块制作定制的sqlite函数(Making custom sqlite functions, based on blocks)
                    • 英文原文出处:
                    相关产品与服务
                    数据库
                    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档