[iOS學(xué)習(xí)筆記]·FMDB:第三方本地?cái)?shù)據(jù)庫處理框架(官方文檔翻譯篇)

目前贾陷,雖然SQLite也為iOS提供了數(shù)據(jù)庫操作方法缘眶,但更多的時(shí)候,一般用FMDB髓废,正如主流APP(如QQ和微信)會(huì)用到巷懈。這里介紹一個(gè)查詢主流APP主要框架的網(wǎng)站:AppSight

1.使用方法(Usage)


FMDB有三個(gè)主要的類:

  • FMDatabase:表示一個(gè)單獨(dú)的SQLite數(shù)據(jù)庫鸵膏。 用來執(zhí)行SQLite的命令。
  • FMResultSet:表示FMDatabase執(zhí)行查詢后結(jié)果集
  • FMDatabaseQueue:如果你想在多線程中執(zhí)行多個(gè)查詢或更新坐慰,你應(yīng)該使用該類较性。這是線程安全的用僧。

1.1 數(shù)據(jù)庫創(chuàng)建(Database Creation)

創(chuàng)建FMDatabase對(duì)象時(shí)參數(shù)為SQLite數(shù)據(jù)庫文件路徑结胀。該路徑可以是以下三種之一:

  • 1.文件路徑。該文件路徑無需真實(shí)存责循,如果不存在會(huì)自動(dòng)創(chuàng)建糟港。
  • 2.空字符串(@"")。表示會(huì)在臨時(shí)目錄創(chuàng)建一個(gè)空的數(shù)據(jù)庫院仿,當(dāng)FMDatabase 鏈接關(guān)閉時(shí)秸抚,文件也被刪除速和。
  • 3.NULL. 將創(chuàng)建一個(gè)內(nèi)在數(shù)據(jù)庫。同樣的剥汤,當(dāng)FMDatabase連接關(guān)閉時(shí)颠放,數(shù)據(jù)會(huì)被銷毀。
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];

(如需對(duì)臨時(shí)數(shù)據(jù)庫或內(nèi)在數(shù)據(jù)庫進(jìn)行一步了解吭敢,請(qǐng)繼續(xù)閱讀:http://www.sqlite.org/inmemorydb.html)

1.2 打開數(shù)據(jù)庫(Opening)

在和數(shù)據(jù)庫交互之前碰凶,數(shù)據(jù)庫必須是打開的。如果資源或權(quán)限不足無法打開或創(chuàng)建數(shù)據(jù)庫鹿驼,都會(huì)導(dǎo)致打開失敗欲低。

if (![db open]) {    
        [db release];   //ARC無需此行
        return;    
    }  

1.3 執(zhí)行更新(Executing Updates)

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

簡(jiǎn)單來說砾莱,只要不是以SELECT開頭的命令都是UPDATE命令。

執(zhí)行更新返回一個(gè)BOOL值凄鼻。YES表示執(zhí)行成功腊瑟,否則表示有那些錯(cuò)誤 。你可以調(diào)用-lastErrorMessage-lastErrorCode方法來得到更多信息块蚌。

執(zhí)行更新的方法是以-executeUpdate:開頭的扫步。

1.4 執(zhí)行查詢(Executing Queries)

SELECT命令就是查詢,執(zhí)行查詢的方法是以-excuteQuery:開頭的匈子。

執(zhí)行查詢時(shí)河胎,如果成功返回FMResultSet對(duì)象,錯(cuò)誤返回nil. 與執(zhí)行更新相當(dāng)虎敦,支持使用 NSError**參數(shù)游岳。同時(shí),你也可以使用-lastErrorCode-lastErrorMessage獲知錯(cuò)誤信息其徙。

為了遍歷查詢結(jié)果胚迫,你可以使用while循環(huán)。你還需要知道怎么跳到下一個(gè)記錄唾那。使用FMDB访锻,很簡(jiǎn)單實(shí)現(xiàn),就像這樣:

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

在你訪問查詢返回值之前闹获,你必須一直調(diào)用-[FMResultSet next] 期犬,即使你只想要一個(gè)記錄:

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的這樣子的方法,參數(shù)是查詢結(jié)果集的列的索引位置避诽。

你無需調(diào)用 [FMResultSet close]來關(guān)閉結(jié)果集, 當(dāng)新的結(jié)果集產(chǎn)生龟虎,或者其數(shù)據(jù)庫關(guān)閉時(shí),會(huì)自動(dòng)關(guān)閉沙庐。

1.5 關(guān)閉數(shù)據(jù)庫(Closing)

當(dāng)使用完數(shù)據(jù)庫鲤妥,你應(yīng)該-close 來關(guān)閉數(shù)據(jù)庫連接來釋放SQLite使用的資源佳吞。

[db close];  

1.6 事務(wù)(Transactions)

FMDatabase是支持事務(wù)的。

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

您可以使用FMDatabaseexecuteStatements:withResultBlock:在字符串中執(zhí)行多個(gè)語句:

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 數(shù)據(jù)格式化(Data Sanitization)

利用一個(gè)SQL語句為FMDB插入數(shù)據(jù)前棉安,你不要嘗試SQL審查(sanitize)任何值底扳。相反的,你應(yīng)該使用標(biāo)準(zhǔn)的SQLite數(shù)據(jù)綁定語法贡耽。

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

?字符由SQLite識(shí)別為要插入的值的占位符花盐。這些執(zhí)行方法全部接受數(shù)量可變的參數(shù)(或這些參數(shù)的一個(gè)代表,例如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]);
}

注意:基本數(shù)據(jù)類型凳宙,如NSInteger變量identifier熙揍,應(yīng)該是一個(gè)NSNumber對(duì)象,通過使用@如上所示的語法實(shí)現(xiàn)氏涩〗烨簦或者您也可以使用[NSNumber numberWithInt:identifier]語法。

同樣是尖,NULL應(yīng)該插入SQL 值[NSNull null]意系。例如,在案件的comment饺汹,這可能是nil(而且是在這個(gè)例子中)蛔添,你可以使用comment ?: [NSNull null]語法,如果將插入字符串comment不是nil兜辞,而是將插入[NSNull null]如果它是nil迎瞧。

在Swift中,您將使用它executeUpdate(values:)逸吵,這不僅僅是一個(gè)簡(jiǎn)潔的Swift語法凶硅,而且也是throws錯(cuò)誤處理正確的錯(cuò)誤:

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那樣包裝基本的數(shù)字類型扫皱。但是如果要插入一個(gè)可選的字符串足绅,你可能會(huì)使用comment ?? NSNull()語法(即,如果是nil韩脑,使用NSNull氢妈,否則使用字符串)。

或者扰才,您可以使用命名參數(shù)語法:

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

參數(shù)名必須以冒名開頭允懂。SQLite本身支持其他字符,但Dictionary key的內(nèi)部實(shí)現(xiàn)是冒號(hào)開頭衩匣,所以注意你的NSDictionary key不要包含冒號(hào)蕾总。

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

關(guān)鍵是不要使用NSString方法stringWithFormat手動(dòng)將值插入SQL語句本身。一個(gè)Swift字符串插入也不應(yīng)該將值插入到SQL中琅捏。使用?占位符將值插入到數(shù)據(jù)庫中(或WHERE在SELECT語句中的子句中使用)生百。

1.9 補(bǔ)充:老版本的README

提供給-executeUpdate:方法的參數(shù)都必須是對(duì)象。就像以下的代碼就無法工作柄延,且會(huì)產(chǎn)生崩潰蚀浆。

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

正確有做法是把數(shù)字打包成 NSNumber對(duì)象

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

或者,你可以使用 -execute*WithFormat: 搜吧,這是NSString風(fēng)格的參數(shù)

[db executeUpdateWithFormat:@"INSERT INTO myTable VALUES (%d)", 42];   

-execute*WithFormat: 的方法的內(nèi)部實(shí)現(xiàn)會(huì)幫你封裝數(shù)據(jù)市俊, 以下這些修飾符都可以使用: %@, %c, %s, %d, %D,%i, %u, %U, %hi, %hu, %qi, %qu, %f, %g, %ld, %lu, %lld, and %llu。 除此之外的修飾符可能導(dǎo)致無法預(yù)知的結(jié)果滤奈。 一些情況下摆昧,你如果要在SQL語句中使用 % 字符,你應(yīng)該使用%%蜒程。

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


在多個(gè)線程中同時(shí)使用一個(gè)FMDatabase實(shí)例是不明智的绅你。一個(gè)線程一個(gè)FMDatabase對(duì)象一直是可以的。只是不要跨線程共享單個(gè)實(shí)例昭躺,絕對(duì)不要同時(shí)跨多個(gè)線程忌锯。否則,意外會(huì)經(jīng)常發(fā)生领炫,程序會(huì)時(shí)不時(shí)崩潰偶垮,或者報(bào)告異常〉酆椋總之很崩潰针史。

所以,不要實(shí)例化單個(gè)FMDatabase對(duì)象碟狞,并在多個(gè)線程中使用啄枕。

而是使用FMDatabaseQueue。實(shí)例化一個(gè)FMDatabaseQueue族沃,并跨多個(gè)線程使用它频祝。該FMDatabaseQueue對(duì)象將同步并協(xié)調(diào)跨多個(gè)線程的訪問。以下是如何使用它:

首先脆淹,讓你的隊(duì)列常空。

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中封裝事務(wù)的簡(jiǎn)單方法:

[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相應(yīng)的版本為:

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將運(yùn)行(序列化隊(duì)列上的)塊(因此是類名)瞭恰。所以如果你同時(shí)從多個(gè)線程調(diào)用FMDatabaseQueue的方法屯曹,它們將按照它們被接收的順序執(zhí)行。這樣查詢和更新將不會(huì)對(duì)對(duì)方的腳趾寄疏,每一個(gè)都很開心是牢。

注意:對(duì)FMDatabaseQueue方法的調(diào)用是阻塞的。所以即使你正在傳遞塊陕截,它們也不會(huì)在另一個(gè)線程上運(yùn)行驳棱。

3. 基于塊制作定制的sqlite函數(shù)(Making custom sqlite functions, based on blocks)


你可以這樣做!例如农曲,-makeFunctionNamed:在main.m中查找

英文原文出處:

https://github.com/ccgus/fmdb

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末社搅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子乳规,更是在濱河造成了極大的恐慌形葬,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暮的,死亡現(xiàn)場(chǎng)離奇詭異笙以,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)冻辩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門猖腕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恨闪,你說我怎么就攤上這事倘感。” “怎么了咙咽?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵老玛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)蜡豹,這世上最難降的妖魔是什么麸粮? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮余素,結(jié)果婚禮上豹休,老公的妹妹穿的比我還像新娘炊昆。我一直安慰自己桨吊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布凤巨。 她就那樣靜靜地躺著视乐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪敢茁。 梳的紋絲不亂的頭發(fā)上佑淀,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音彰檬,去河邊找鬼味廊。 笑死艾杏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播憔恳,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼结执!你這毒婦竟也來了食拜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤亮蒋,失蹤者是張志新(化名)和其女友劉穎扣典,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慎玖,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贮尖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了趁怔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湿硝。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖痕钢,靈堂內(nèi)的尸體忽然破棺而出图柏,到底是詐尸還是另有隱情,我是刑警寧澤任连,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布蚤吹,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏裁着。R本人自食惡果不足惜繁涂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望二驰。 院中可真熱鬧扔罪,春花似錦、人聲如沸桶雀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矗积。三九已至全肮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棘捣,已是汗流浹背辜腺。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乍恐,地道東北人评疗。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像茵烈,于是被迫代替她去往敵國和親百匆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容