一直在構思這篇文章要寫一些什么內容睬愤,抱著負責任的態(tài)度,去看了FMDB的文檔劲蜻,發(fā)現文檔不是很長陆淀,并且基本把該寫的東西全部都寫了,于是我決定這篇文章就是把FMDB的文檔翻譯一遍先嬉。
噴子們看清楚了轧苫,我這里寫了是把文檔翻譯了一遍。
(有的地方真的是只可意會啊疫蔓,翻譯成中文就變得怪怪的)
installed#
你可以使用CocoaPods來安裝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
當然你也可以直接拖入源文件含懊,到https://github.com/ccgus/fmdb 下載源文件,然后直接將scr->fmdb文件夾拖入到你的工程就OK衅胀。
當然你需要添加依賴庫:libsqlite3.dylib
(xcode7以后你需要添加的是libsqlite3.tbd)
拖入源文件岔乔,并且添加依賴庫以后你就可以使用FMDB了,當然你需要引用頭文件
<code>#import "FMDB.h"</code>
ARC還是MRC#
隨你便啦滚躯,FMDB很聰明的重罪,他可以根據你的工程來執(zhí)行正確的操作,所以你不用擔心他會做一些不正確的事情而造成一些不好的后果哀九。
*(不過我只在ARC下使用過剿配,并沒有真正的在MRC下驗證過,不過文檔中說是沒有問題)
使用#
在FMDB中主要有三個類:
1阅束、<code>FMDatabase</code> - 簡單的說這個類就是代表了數據庫
2呼胚、<code>FMResultSet</code> - 這個類表示查詢操作的結果
3、<code>FMDatabaseQueue</code> - 多線程操作的時候你會用到這個類息裸,并且這是線程安全蝇更,具體怎么用后面再說。
數據庫的創(chuàng)建#
你需要使用一個path來創(chuàng)建一個本地FMDatabase數據庫呼盆,這個path有三種類型:
1年扩、你可以使用一個本地的地址來創(chuàng)建這個數據庫,這個地址不一定真實存在访圃,如果不存在厨幻,那么FMDB會創(chuàng)建這個數據庫并返回,存在則直接返回這個數據庫腿时。
2况脆、一個空字符串@"".FMDB會在本地創(chuàng)建一個臨時的數據庫,當數據庫關閉的時候會刪除這個數據庫批糟。
3格了、NULL.如果你將這個path填的是NULL,那么這個數據被創(chuàng)建在內存中徽鼎,數據庫關閉的時候被銷毀盛末。
(更多信息關于臨時數據庫和在內存中的數據庫弹惦,你可以閱讀這篇文檔 http://www.sqlite.org/inmemorydb.html )
FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"];
Opening#
使用數據庫之前你必須打開數據庫。
if (![db open]) {
[db release];
return;
}
Executing Updates
所有不是select操作的操作都算update. 包括 CREATE, UPDATE, INSERT, ALTER, COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE ····換句話說也就是如果你的操作不是以SELECT開頭的都是update操作.
update操作返回一個布爾值悄但,YES表示操作成功肤频,NO表示你可以遇到了一些錯誤.
<code>FMDatabase</code>有兩個方法 -lastErrorMessage 和 -lastErrorCode,你可以使用這兩個方法來查看錯誤算墨。
Executing Queries#
SELECT 查詢使用 -executeQuery... 方法.
查詢成功返回<code> FMResultSet</code>,失敗則是返回nil.
同樣你可以使用<code>FMDatabase</code>的兩個方法 -lastErrorMessage and -lastErrorCode 來查找原因宵荒。
你需要用一個循環(huán)來獲取到查詢到的每一個值。like this:
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:
UTF8StringForColumnName:
objectForColumnName:
以上的每個方法都有對應的 {type}ForColumnIndex: 上面那一溜方法是用例的名字來獲取數據报咳,而這個方法則是用數據在查詢結果中對應的位置來獲取數據
當你使用完了<code>FMResultSet</code>以后你不需要close FMResultSet因為當<code>FMDatabase</code>關閉以后<code>FMResultSet</code>也同樣會關閉。
Closing#
用完了FMDB記得關挖藏。
[db close];
批處理#
<code>FMDatabase</code>的方法 executeStatements: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;
}];
數據處理#
你必須使用標準的SQLite的標準語法,像下面那樣(而不是SQL中那樣):
INSERT INTO myTable VALUES (?, ?, ?, ?)
‘膜眠?’這個符號表示插入數據的替代符岩臣,操作方法會接收參數來替代這個符號 (或者是代表這些參數的,比如說: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:這里需要注意的是宵膨,如果是基本數據類型比如說NSInteger架谎,你需要轉化為NSNumber,你可以使用[ NSNumber numberwithint:identifier]這樣的語法或者是標識符語法:@(identifier)辟躏。
如果是插入nil谷扣,那么你不能直接插入nil,而是需要插入[NSNull null]捎琐,像上面那個例子中寫的是:comment ?: [NSNull null]会涎,那么如果commit是nil的話則會插入nil,反之則會插入commit.
下面這種書寫方法和上面表達的是同一個意思瑞凑。
INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)
像上面這種寫法參數是以冒號開頭的末秃,SQLite支持其他字符,但是在字典中key都是以冒號為前綴的籽御,所以你的字典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的方法比如說<code> stringWithFormat</code>來手動插入參數练慕,必須要使用Values(?,?)這樣的方法篱蝇,把贺待?當作替代符徽曲。(不要自作聰明)
FMDatabaseQueue 是線程安全的
不要在多個線程之間使用同一個<code> FMDatabase</code>對象零截,最好是每一個線程都有一個獨立的<code> FMDatabase</code>對象,如果你在多線程之間使用同一個對象秃臣,那么會有不好的事情發(fā)生查排,你的app可能會經常崩潰,甚至有隕石落下來砸到你的MAC.(文檔里就是這么唬人的)
如果你需要在多線程中使用<code> FMDatabase</code>,那么請使用<code> FMDatabaseQueue</code>奥吩,下面是他的使用方法:
(其實如果我們查看FMDB的源代碼我們可以發(fā)現其實FMDB在后臺也是使用GCD來實現的)
首先創(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];
}];