FMDB 2.7.12

FMDB 2.7.12

测试已测试
语言 Obj-CObjective C
许可 未知
发布最后发布2024年7月

Gus MuellerStephan HeilnerRobert RyanJustin R. Miller维护。



FMDB 2.7.12

  • August Mueller

FMDB v2.7

CocoaPods Compatible Carthage Compatible

这是围绕 SQLite 的 Objective-C 封装:https://sqlite.ac.cn/

FMDB 邮件列表

http://groups.google.com/group/fmdb

阅读 SQLite FAQ

http://www.sqlite.org/faq.html

由于 FMDB 是基于 SQLite 构建的,您应该至少从头到尾阅读此页面一次。同时,请确保将 SQLite 文档页面书签:http://www.sqlite.org/docs.html

贡献

您是否有应加入 FMDB 的优秀想法?您可能需要先联系 ccgus,确保他还没有因为某些原因排除它。否则,请提交拉取请求,并确保您遵守本地编码约定。然而,请耐心等待,如果您一周或更长时间没有收到 ccgus 的回复,您可能需要发送一条消息询问发生了什么。

安装

CocoaPods

Dependency Status Reference Status

可以使用CocoaPods安装FMDB。

如果尚未进行,则可能需要初始化项目,以便为您生成Podfile模板。

$ pod init

然后,编辑Podfile,添加FMDB

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'MyApp' do
    # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
    use_frameworks!

    # Pods for MyApp2

    pod 'FMDB'
    # pod 'FMDB/FTS'   # FMDB with FTS
    # pod 'FMDB/standalone'   # FMDB with latest SQLite amalgamation source
    # pod 'FMDB/standalone/FTS'   # FMDB with latest SQLite amalgamation source and FTS
    # pod 'FMDB/SQLCipher'   # FMDB with SQLCipher
end

然后安装pods。

$ pod install

然后打开的是.xcworkspace而不是.xcodeproj

有关CocoaPods的更多信息,请访问https://cocoapods.org.cn

如果使用FMDB与SQLCipher,则必须使用FMDB/SQLCipher subspec。FMDB/SQLCipher subspec将SQLCipher声明为依赖项,这允许FMDB使用-DSQLITE_HAS_CODEC标志进行编译。

Carthage

确保您有Carthage的最新版本后,您可以打开命令行终端,切换到您项目的主目录,然后执行以下命令。

$ echo ' github "ccgus/fmdb" ' > ./Cartfile
$ carthage update

然后,您可以按照Carthage的入门指南(即对于iOS,在目标中添加框架到“链接二进制库”并添加copy-frameworks脚本;在macOS中,将框架添加到“嵌入式二进制文件”列表)配置您的项目。

FMDB类参考

http://ccgus.github.io/fmdb/html/index.html

自动引用计数(ARC)或手动内存管理?

您可以在您的Cocoa项目中使用任意一种样式。FMDB会在编译时确定您使用的是哪种样式,并做出正确的处理。

FMDB 2.7的新功能

FMDB 2.7试图支持更自然的界面。这对Swift开发者来说是一个相当重大的变化(经过nullability审计;将外部接口中的属性修改为方法,等等)。对于Objective-C开发者来说,这应该是一个相对无缝的过渡(除非您之前在公共接口中使用了已公开的ivars,但实际上您不应该这样做!)

可空性和Swift的可选对象

FMDB 2.7大体上与之前版本相同,但已经过可空性审计。对于Objective-C用户来说,这意味着如果您对基于FMDB的项目进行静态分析,在审查项目时可能会收到更有意义的警告,但可能不需要在代码中进行任何修改。

对于Swift用户来说,这次可空性审计导致了一些变化,这些变化与FMDB 2.6不完全向后兼容,但更加Swifty。在FMDB进行可空性审计之前,Swift被迫防御性地假设变量是可选的,但现在库更准确地知道哪些属性和方法参数是可选的,哪些不是。

这意味着,尽管如此,为FMDB 2.7编写的Swift代码可能需要修改。例如,考虑以下适用于FMDB 2.6的Swift 3/Swift 4代码

queue.inTransaction { db, rollback in
    do {
        guard let db == db else {
            // handle error here
            return
        }

        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [1])
        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [2])
    } catch {
        rollback?.pointee = true
    }
}

因为FMDB 2.6没有进行可空性审计,Swift推断出 dbrollback 是可选的。但,现在,在FMDB 2.7中,Swift现在知道例如,db 和上面的 rollback 都不能是 nil,因此它们不再是可选的。因此

queue.inTransaction { db, rollback in
    do {
        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [1])
        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [2])
    } catch {
        rollback.pointee = true
    }
}

自定义函数

在以前编写自定义函数时,您通常需要自己包含自己的 @autoreleasepool 块,以避免编写遍历大量表时出现的问题。现在,FMDB会自动将其包装在一个autorelease池中,因此您不必这样做。

此外,在过去,在检索传递给函数的值时,您必须降至SQLite C API并包含您自己的sqlite3_value_XXX调用。现在有了FMDatabase方法,如valueIntvalueString等,因此您可以保持在使用Swift和/或Objective-C的同时,无需自己调用C函数。同样,当指定返回值时,您不再需要调用C API的sqlite3_result_XXX,而是可以使用FMDatabase方法,如resultIntresultString等。有一个名为SqliteValueType的新enum,用于检查传递给自定义函数的参数类型。

因此,您可以在Swift 3中做类似的事情.

db.makeFunctionNamed("RemoveDiacritics", arguments: 1) { context, argc, argv in
    guard db.valueType(argv[0]) == .text || db.valueType(argv[0]) == .null else {
        db.resultError("Expected string parameter", context: context)
        return
    }

    if let string = db.valueString(argv[0])?.folding(options: .diacriticInsensitive, locale: nil) {
        db.resultString(string, context: context)
    } else {
        db.resultNull(context: context)
    }
}

然后您可以将该函数用于您的SQL(在这种情况下,匹配“Jose”和“José”)

SELECT * FROM employees WHERE RemoveDiacritics(first_name) LIKE 'jose'

注意,方法makeFunctionNamed:maximumArguments:withBlock:已被重命名为makeFunctionNamed:arguments:block:,以更准确地反映第二个参数的函数意图。

API变更

除了上面提到的makeFunctionNamed之外,还有一些其他的API变更。特别是,

  • 为了与其他API保持一致,方法objectForColumnNameUTF8StringForColumnName已被重命名为objectForColumnUTF8StringForColumn

  • 注意,如果传递了无效的列名/索引,现在objectForColumn(以及相关的索引运算符)返回nil。它以前返回NSNull

  • 为了避免与执行事务的FMDatabaseQueue方法inTransaction混淆,用来确定您是否在事务中,原来的inTransaction方法已被替换为只读属性isInTransaction

  • 一些函数已被转换为属性,包括databasePathmaxBusyRetryTimeIntervalshouldCacheStatementssqliteHandlehasOpenResultSetslastInsertRowIdchangesgoodConnectioncolumnCountresultDictionaryapplicationIDapplicationIDStringuserVersioncountOfCheckedInDatabasescountOfCheckedOutDatabasescountOfOpenDatabases。对于Objective-C用户来说,影响不大,但对于Swift用户来说,则使得接口更加自然。注意:对于Objective-C开发者来说,以前版本的FMDB公开了许多实例变量(但我们希望您并未直接使用它们!),但不再公开这些实现细节。

URL方法

随着Apple从路径转向URL,现在有各种init方法的NSURL版本,以前只接受路径。

用法

FMDB 有三个主要类

  1. FMDatabase - 代表单个 SQLite 数据库。用于执行 SQL 语句。
  2. FMResultSet - 代表在 FMDatabase 上执行查询的结果。
  3. FMDatabaseQueue - 如果您想要在多个线程中执行查询和更新,您将想要使用这个类。它将在下面的“线程安全性”部分中进行描述。

数据库创建

使用路径创建 FMDatabase 来指向 SQLite 数据库文件。此路径可以是以下三者之一:

  1. 文件系统路径。文件不必在磁盘上存在。如果不存在,它将为您创建。
  2. 空字符串(@"")。在临时位置创建空数据库。当 FMDatabase 连接关闭时,此数据库将被删除。
  3. NULL。创建内存数据库。当 FMDatabase 连接关闭时,此数据库将被销毁。

(有关临时和内存数据库的更多信息,请参阅有关该主题的 sqlite 文档:http://www.sqlite.org/inmemorydb.html

NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];

打开

在与数据库交互之前,它必须被打开。如果资源不足或没有权限打开和/或创建数据库,则打开会失败。

if (![db open]) {
    // [db release];   // uncomment this line in manual referencing code; in ARC, this is not necessary/permitted
    db = nil;
    return;
}

执行更新

任何非 SELECT 语句的 SQL 语句都视为更新。这包括 CREATEUPDATEINSERTALTERCOMMITBEGINDETACHDELETEDROPENDEXPLAINVACUUMREPLACE 语句(及更多)。基本上,如果您的 SQL 语句不以 SELECT 开头,它就是一个更新语句。

执行更新返回一个单一值,一个 BOOL。返回值为 YES 表示更新已成功执行,而返回值为 NO 表示遇到某些错误。您可以调用 -lastErrorMessage-lastErrorCode 方法以获取更多信息。

执行查询

一个 SELECT 语句是一个查询,通过 -executeQuery... 方法之一执行。

执行查询成功将返回一个 FMResultSet 对象,失败时返回 nil。您应使用 -lastErrorMessage-lastErrorCode 方法来确定查询失败的原因。

为了遍历查询结果,您可以使用 while() 循环。您还需要从一行跳到另一行。在 FMDB 中,这样做最简单的方式如下

FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
    //retrieve values for each record
}

在任何时候尝试访问查询返回的值之前,都必须调用 -[FMResultSet next],即使您只期望一个结果也是如此

FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"];
if ([s next]) {
    int totalCount = [s intForColumnIndex:0];
}

FMResultSet 有许多方法以适当格式检索数据

  • intForColumn
  • longForColumn
  • longLongIntForColumn
  • boolForColumn
  • doubleForColumn
  • stringForColumn
  • dateForColumn
  • dataForColumn
  • dataNoCopyForColumn
  • UTF8StringForColumn
  • objectForColumn

这些方法也有 {type}ForColumnIndex: 的变体,可用于根据结果中列的位置检索数据,而不是根据列的名称。

通常,没有必要自己 -close 一个 FMResultSet,因为当结果集被释放,或者父数据库关闭时,这将自动发生。

关闭

在完成数据库的查询和更新后,您应该调用 -close 来关闭 FMDatabase 连接,这样 SQLite 就会释放其在操作过程中获取的所有资源。

[db close];

事务

FMDatabase 可以通过调用相应的方法或执行 begin/end 事务语句来开始和提交事务。

多条语句和批处理相关内容

您可以使用 FMDatabaseexecuteStatements:withResultBlock: 方法在字符串中执行多条语句。

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

数据清洗

当向FMDB提供SQL语句时,不应在插入之前尝试“清洗”任何值。而是应该使用标准的SQLite绑定语法

INSERT INTO myTable VALUES (?, ?, ?, ?)

SQLite将?字符识别为要插入的值的占位符。所有执行方法都接受可变数量的参数(或这些参数的表示形式,如NSArrayNSDictionaryva_list),这些参数将被正确转义。

然后,在Objective-C中使用这些SQL语句中的?占位符

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

注意:基本数据类型,如变量NSIntegeridentifier,应该作为NSNumber对象使用,如上面所示使用@语法。或者您还可以使用[NSNumber numberWithInt:identifier]语法。

同样,SQL中的NULL值应该插入为[NSNull null]。例如,在comment可能是nil(在本例中就是这样),您可以使用comment ?: [NSNull null]语法,如果comment不是nil,将插入字符串;如果它是nil,将插入[NSNull null]

在Swift中,您将使用executeUpdate(values:),这不仅是一个简洁的Swift语法,而且还能抛出错误进行适当的错误处理

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,否则使用字符串)。

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

INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)

参数必须以冒号开头。SQLite本身支持其他字符,但内部字典键是以冒号作为前缀的,不要在您的字典键中包含冒号。

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中。对于插入到数据库中的值(或在SELECT语句的WHERE子句中使用的值),使用?占位符。

使用FMDatabaseQueue和线程安全。

同时从多个线程使用单个FMDatabase实例是一个糟糕的主意。制作一个FMDatabase对象作为每个线程是始终可行的。只是不要在多个线程之间共享单个实例,更不用说同时跨多个线程共享单个实例。最终会发生坏事,您最终会遇到崩溃或其他异常,甚至可能是流星撞击您的Mac Pro。这不怎么样。

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

相反,使用FMDatabaseQueue。实例化单个FMDatabaseQueue并在多个线程中使用它。FMDatabaseQueue对象将同步和协调多个线程的访问。以下是如何使用它的方法

首先,创建您的队列。

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];

然后这样使用它

[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]) {
        …
    }
}];

在事务中结束事物的一个简单方法可以这样做:

[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的等效方法会是:

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的方法调用是阻塞的。所以尽管你传递了块,它们将不会在另一个线程上运行。

基于块的SQLite自定义函数。

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

Swift

你同样可以在Swift项目中使用FMDB。

要做到这一点,你必须

  1. 将FMDB src 文件夹中的相关.m.h 文件复制到你的项目。

你可以全部复制(这最简单),或者只复制你需要的文件。你可能至少需要FMDatabaseFMResultSetFMDatabaseAdditions 提供了一些非常有用的方便方法,因此你也可能想要它。如果你的数据库访问是多线程的话,使用FMDatabaseQueue 会非常有用。但是,如果你选择不复制整个src目录中的所有文件,那么你可能需要更新FMDB.h以仅引用你项目中包含的文件。

注意,如果你将src文件夹中的所有文件复制到你的项目中(这是一种推荐的做法),你可能想要将单个文件拖动到你的项目中,而不是拖动文件夹,因为如果你拖动文件夹,你不会收到添加桥接头(见下一点)的提示。

  1. 如果有提示创建“桥接头”,你应该这样做。如果没有提示,并且你没有桥接头,请添加一个。

关于桥接头的信息,请参阅同一项目中混用Swift和Objective-C

  1. 在你的桥接头中,添加一行文本。

    #import "FMDB.h"
  2. 使用executeQueryexecuteUpdate的变体,配合sqlvalues参数以及try模式,如下所示。这些形式的executeQueryexecuteUpdate都像Swift原生一样抛出错误。

如果进行以上操作,则可以编写使用FMDatabase的Swift代码。例如,在Swift 3中

let fileURL = try! FileManager.default
    .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    .appendingPathComponent("test.sqlite")

let database = FMDatabase(url: fileURL)

guard database.open() else {
    print("Unable to open database")
    return
}

do {
    try database.executeUpdate("create table test(x text, y text, z text)", values: nil)
    try database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", values: ["a", "b", "c"])
    try database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", values: ["e", "f", "g"])

    let rs = try database.executeQuery("select x, y, z from test", values: nil)
    while rs.next() {
        if let x = rs.string(forColumn: "x"), let y = rs.string(forColumn: "y"), let z = rs.string(forColumn: "z") {
            print("x = \(x); y = \(y); z = \(z)")
        }
    }
} catch {
    print("failed: \(error.localizedDescription)")
}

database.close()

历史

历史和更改记录可在其GitHub页面上找到,并在"CHANGES_AND_TODO_LIST.txt"文件中进行了总结。

贡献者

对FMDB做出贡献的人员包含在"Contributors.txt"文件中。

其他可能对有见识的研发者感兴趣的项目使用FMDB。

关于FMDB编码风格的快速提示

使用空格,而不是制表符。使用方括号,而不是点符号。观察FMDB如何使用花括号等,并坚持使用该样式。

报告错误

将你的错误缩小到尽可能少的代码量。你希望开发者能轻松地看到和重现你的错误。如果你有帮助,假装能够修复你错误的人正在同时发布3个大产品,参与多个开源项目,还有一个新出生的孩子,总的来说非常非常忙碌。

我们还在FMDB分发的main.m(FMDBReportABugFunction)中添加了一个模板函数来帮助你。

  • 在Xcode中打开fmdb项目。
  • 打开main.m并修改FMDBReportABugFunction以重现你的错误。
    • 在代码中设置你的表。
    • 执行查询或更新。
    • 添加一些演示错误的断言。

然后你可以通过展示你整洁而紧凑的FMDBReportABugFunction,或者在FMDB邮件列表上发布,或者通过github FMDB错误报告器报告错误。

可选

找出错误所在,修复它,发送补丁或者在该邮件列表上提出。确保所有其他测试都是在你的修改之后进行的。

支持

FMDB的支持渠道是邮件列表(见上方)、在这里提交错误报告或者在Stack Overflow上。也就是说,支持是由社区在自愿的基础上提供的。

FMDB的开发由Flying Meat的Gus Mueller监督。如果FMDB对你有帮助,请考虑购买FM的应用或告诉所有的朋友。

授权

FMDB的授权包含在"License.txt"文件中。

如果你在酒吧里遇到了Gus Mueller或Rob Ryan,如果你觉得FMDB对你有帮助,你可以考虑买他们想喝的饮料。

当然,饮料是为他们准备的,如果你试图占便宜可就丢脸了。