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

目前,虽然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连接关闭时,数据会被销毁。
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];

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

1.2 打开数据库(Opening)

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

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,很简单实现,就像这样:

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:
  • dataForColumn:
  • dataNoCopyForColumn:
  • UTF8StringForColumnIndex:
  • objectForColumn:

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

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

1.5 关闭数据库(Closing)

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

[db close];  

1.6 事务(Transactions)

FMDatabase是支持事务的。

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

您可以使用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;
}];

1.8 数据格式化(Data Sanitization)

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

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

?字符由SQLite识别为要插入的值的占位符。这些执行方法全部接受数量可变的参数(或这些参数的一个代表,例如NSArray,NSDictionaryva_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]);
}

注意:基本数据类型,如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错误处理正确的错误:

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本身支持其他字符,但Dictionary key的内部实现是冒号开头,所以注意你的NSDictionary key不要包含冒号。

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:方法的参数都必须是对象。就像以下的代码就无法工作,且会产生崩溃。

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", 42];   

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

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:42]];   

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

[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对象将同步并协调跨多个线程的访问。以下是如何使用它:

首先,让你的队列。

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 ]){ 
        ... 
    } 
}]

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

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

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


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

英文原文出处:

https://github.com/ccgus/fmdb

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容