目前贾陷,雖然SQLite也為iOS提供了數(shù)據(jù)庫操作方法缘眶,但更多的時(shí)候,一般用FMDB髓废,正如主流APP(如QQ和微信)會(huì)用到巷懈。這里介紹一個(gè)查詢主流APP主要框架的網(wǎng)站:AppSight 。
這篇文章慌洪,主要挑選FMDB官方文檔中的使用方法部分進(jìn)行了翻譯顶燕。關(guān)于Pod以及Carthage安裝第三方庫的部分,可以參考筆者相關(guān)文章(Pod冈爹,Carthage)涌攻。FMDB官方源碼地址傳送門:https://github.com/ccgus/fmdb 。
FMDB是SQLite的Objective-C包裝器:http://sqlite.org/ 频伤。由于FMDB是建立在SQLite之上的恳谎,所以您至少閱讀相關(guān)頁面一次:http://www.sqlite.org/docs.html,http://www.sqlite.org/faq.html 憋肖。
只看官方文檔是不夠的因痛,看完后要多融合介紹的這些方法進(jìn)行練習(xí)。關(guān)于FMDB的使用示例代碼和DEMO可以參考筆者的另一篇文章http://www.reibang.com/p/18cd2416ccc3 岸更。
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菇爪,NSDictionary
或va_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中查找