一直在構(gòu)思這篇文章要寫(xiě)一些什么內(nèi)容,抱著負(fù)責(zé)任的態(tài)度,去看了FMDB的文檔,發(fā)現(xiàn)文檔不是很長(zhǎng),并且基本把該寫(xiě)的東西全部都寫(xiě)了,于是我決定這篇文章就是把FMDB的文檔翻譯一遍。
噴子們看清楚了,我這里寫(xiě)了是把文檔翻譯了一遍。
(有的地方真的是只可意會(huì)啊,翻譯成中文就變得怪怪的)
installed#
你可以使用CocoaPods來(lái)安裝FMDB
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
當(dāng)然你也可以直接拖入源文件,到https://github.com/ccgus/fmdb 下載源文件,然后直接將scr->fmdb文件夾拖入到你的工程就OK。
當(dāng)然你需要添加依賴庫(kù):libsqlite3.dylib
(xcode7以后你需要添加的是libsqlite3.tbd)
拖入源文件,并且添加依賴庫(kù)以后你就可以使用FMDB了,當(dāng)然你需要引用頭文件
<code>#import "FMDB.h"</code>
ARC還是MRC#
隨你便啦,F(xiàn)MDB很聰明的,他可以根據(jù)你的工程來(lái)執(zhí)行正確的操作,所以你不用擔(dān)心他會(huì)做一些不正確的事情而造成一些不好的后果。
*(不過(guò)我只在ARC下使用過(guò),并沒(méi)有真正的在MRC下驗(yàn)證過(guò),不過(guò)文檔中說(shuō)是沒(méi)有問(wèn)題)
使用#
在FMDB中主要有三個(gè)類:
1、<code>FMDatabase</code> - 簡(jiǎn)單的說(shuō)這個(gè)類就是代表了數(shù)據(jù)庫(kù)
2、<code>FMResultSet</code> - 這個(gè)類表示查詢操作的結(jié)果
3、<code>FMDatabaseQueue</code> - 多線程操作的時(shí)候你會(huì)用到這個(gè)類,并且這是線程安全,具體怎么用后面再說(shuō)。
數(shù)據(jù)庫(kù)的創(chuàng)建#
你需要使用一個(gè)path來(lái)創(chuàng)建一個(gè)本地FMDatabase數(shù)據(jù)庫(kù),這個(gè)path有三種類型:
1、你可以使用一個(gè)本地的地址來(lái)創(chuàng)建這個(gè)數(shù)據(jù)庫(kù),這個(gè)地址不一定真實(shí)存在,如果不存在,那么FMDB會(huì)創(chuàng)建這個(gè)數(shù)據(jù)庫(kù)并返回,存在則直接返回這個(gè)數(shù)據(jù)庫(kù)。
2、一個(gè)空字符串@"".FMDB會(huì)在本地創(chuàng)建一個(gè)臨時(shí)的數(shù)據(jù)庫(kù),當(dāng)數(shù)據(jù)庫(kù)關(guān)閉的時(shí)候會(huì)刪除這個(gè)數(shù)據(jù)庫(kù)。
3、NULL.如果你將這個(gè)path填的是NULL,那么這個(gè)數(shù)據(jù)被創(chuàng)建在內(nèi)存中,數(shù)據(jù)庫(kù)關(guān)閉的時(shí)候被銷毀。
(更多信息關(guān)于臨時(shí)數(shù)據(jù)庫(kù)和在內(nèi)存中的數(shù)據(jù)庫(kù),你可以閱讀這篇文檔 http://www.sqlite.org/inmemorydb.html )
FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"];
Opening#
使用數(shù)據(jù)庫(kù)之前你必須打開(kāi)數(shù)據(jù)庫(kù)。
if (![db open]) {
[db release];
return;
}
Executing Updates
所有不是select操作的操作都算update. 包括 CREATE, UPDATE, INSERT, ALTER, COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE ····換句話說(shuō)也就是如果你的操作不是以SELECT開(kāi)頭的都是update操作.
update操作返回一個(gè)布爾值,YES表示操作成功,NO表示你可以遇到了一些錯(cuò)誤.
<code>FMDatabase</code>有兩個(gè)方法 -lastErrorMessage 和 -lastErrorCode,你可以使用這兩個(gè)方法來(lái)查看錯(cuò)誤。
Executing Queries#
SELECT 查詢使用 -executeQuery... 方法.
查詢成功返回<code> FMResultSet</code>,失敗則是返回nil.
同樣你可以使用<code>FMDatabase</code>的兩個(gè)方法 -lastErrorMessage and -lastErrorCode 來(lái)查找原因。
你需要用一個(gè)循環(huán)來(lái)獲取到查詢到的每一個(gè)值。like this:
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
//retrieve values for each record
}
不管如何你都必須使用方法 -[FMResultSet next]來(lái)獲取到每一個(gè)查詢結(jié)果。
FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"];
if ([s next]) {
int totalCount = [s intForColumnIndex:0];
}
FMResultSet 有許多類型用來(lái)返回不同類型的查詢結(jié)果的值
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumnName:
objectForColumnName:
以上的每個(gè)方法都有對(duì)應(yīng)的 {type}ForColumnIndex: 上面那一溜方法是用例的名字來(lái)獲取數(shù)據(jù),而這個(gè)方法則是用數(shù)據(jù)在查詢結(jié)果中對(duì)應(yīng)的位置來(lái)獲取數(shù)據(jù)
當(dāng)你使用完了<code>FMResultSet</code>以后你不需要close FMResultSet因?yàn)楫?dāng)<code>FMDatabase</code>關(guān)閉以后<code>FMResultSet</code>也同樣會(huì)關(guān)閉。
Closing#
用完了FMDB記得關(guān)。
[db close];
批處理#
<code>FMDatabase</code>的方法 executeStatements:withResultBlock:可以使用字符串來(lái)同時(shí)處理多條指令。
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;
}];
數(shù)據(jù)處理#
你必須使用標(biāo)準(zhǔn)的SQLite的標(biāo)準(zhǔn)語(yǔ)法,像下面那樣(而不是SQL中那樣):
INSERT INTO myTable VALUES (?, ?, ?, ?)
‘?’這個(gè)符號(hào)表示插入數(shù)據(jù)的替代符,操作方法會(huì)接收參數(shù)來(lái)替代這個(gè)符號(hào) (或者是代表這些參數(shù)的,比如說(shuō):NSArray, NSDictionary, va_list).
OC中你可以像下面這樣使用:
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]);
}
Note:這里需要注意的是,如果是基本數(shù)據(jù)類型比如說(shuō)NSInteger,你需要轉(zhuǎn)化為NSNumber,你可以使用[ NSNumber numberwithint:identifier]這樣的語(yǔ)法或者是標(biāo)識(shí)符語(yǔ)法:@(identifier)。
如果是插入nil,那么你不能直接插入nil,而是需要插入[NSNull null],像上面那個(gè)例子中寫(xiě)的是:comment ?: [NSNull null],那么如果commit是nil的話則會(huì)插入nil,反之則會(huì)插入commit.
下面這種書(shū)寫(xiě)方法和上面表達(dá)的是同一個(gè)意思。
INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)
像上面這種寫(xiě)法參數(shù)是以冒號(hào)開(kāi)頭的,SQLite支持其他字符,但是在字典中key都是以冒號(hào)為前綴的,所以你的字典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)鍵的一點(diǎn)就是:千萬(wàn)不要用NSString的方法比如說(shuō)<code> stringWithFormat</code>來(lái)手動(dòng)插入?yún)?shù),必須要使用Values(?,?)這樣的方法,把?當(dāng)作替代符。(不要自作聰明)
FMDatabaseQueue 是線程安全的
不要在多個(gè)線程之間使用同一個(gè)<code> FMDatabase</code>對(duì)象,最好是每一個(gè)線程都有一個(gè)獨(dú)立的<code> FMDatabase</code>對(duì)象,如果你在多線程之間使用同一個(gè)對(duì)象,那么會(huì)有不好的事情發(fā)生,你的app可能會(huì)經(jīng)常崩潰,甚至有隕石落下來(lái)砸到你的MAC.(文檔里就是這么唬人的)
如果你需要在多線程中使用<code> FMDatabase</code>,那么請(qǐng)使用<code> FMDatabaseQueue</code>,下面是他的使用方法:
(其實(shí)如果我們查看FMDB的源代碼我們可以發(fā)現(xiàn)其實(shí)FMDB在后臺(tái)也是使用GCD來(lái)實(shí)現(xiàn)的)
首先創(chuàng)建你的線程
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]) {
…
}
}];
An easy way to wrap things up in a transaction can be done like this:
[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…
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @4];
}];