iOS中原生的SQLite API在使用上相當不友好桐款,在使用時,非常不便佳吞。于是拱雏,就出現(xiàn)了一系列將SQLite API進行封裝的庫,例如FMDB,PlausibleDatabase等.
安裝
使用CocoaPods來安裝FMDB
CocoaPods是Swift和Objective-C Cocoa項目的依賴管理器底扳。它擁有4萬多個庫铸抑,用于超過280萬個應用程序。CocoaPods可以幫助您優(yōu)雅地擴展您的項目衷模。
如果你沒有創(chuàng)建CocoaPods工程,在工程目錄下使用
$ pod init
來初始化項目,在生成的Podfile文件中添加FMDB:
target 'MyApp' do
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
end
然后執(zhí)行
$ pod install
然后使用新生成的*.xcworkspace
工程,而不是*.xcodeproj
鹊汛。
用法
在FMDB中有三個主要的類;
-
FMDataase
-表示一個SQLite數(shù)據(jù)庫.用于執(zhí)行SQLite語句 -
FMResultSet
-表示數(shù)據(jù)庫的查詢結(jié)果 -
FMDatabaseQueue
-在多個線程來執(zhí)行查詢和更新時會使用這個類
創(chuàng)建數(shù)據(jù)庫
需要制定一個文件路徑(path)來創(chuàng)建一個給予SQLite的FMDatabase
,有以下三種方式可以指定文件路徑:
- 指定一個文件路徑,如果文件不存在,則自動創(chuàng)建
- 指定路徑為一個空字符串(
@""
),使用該方式會創(chuàng)建一個臨時數(shù)據(jù)庫文件,當FMDatabase
被關(guān)閉的時候,臨時數(shù)據(jù)庫文件自動被刪除 -
NULL
會在內(nèi)存中創(chuàng)建一個臨時數(shù)據(jù)庫,在FMDatabase
被關(guān)閉的時候自動刪除
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
打開數(shù)據(jù)庫
在與數(shù)據(jù)庫進行交互之前,我們需要打開數(shù)據(jù)庫,如果沒有足夠的資源或權(quán)限打開和/或創(chuàng)建數(shù)據(jù)庫菇爪,則打開失敗,當打開失敗的時候,把db
置為nil
.
if(![db open]){
db = nil;
return ;
}
執(zhí)行更新
除了SELECT
操作外,SQLite的所有命令都使用executeUpdate
消息來執(zhí)行.包括CREATE
, UPDATE
, INSERT
, ALTER
, COMMIT
, BEGIN
, DETACH
, DELETE
, DROP
, END
, EXPLAIN
, VACUUM
,REPLACE
等,只要你不是執(zhí)行SELECT
語句,就是更新數(shù)據(jù)庫操作.
BOOL succ = [db executeUpdate:sql]; //sql是SQLite命令語句字符串
執(zhí)行更新會返回一個布爾值。返回值YES意味著更新成功執(zhí)行柒昏,返回值NO意味著遇到了一些錯誤∥踝幔可以調(diào)用lastErrorMessage
和lastErrorCode
方法來檢索更多的信息职祷。
執(zhí)行查詢
一個SELECT
語句是一個查詢,并通過其中一種-executeQuery...
方法執(zhí)行届囚。
如果成功執(zhí)行查詢則返回一個FMResultSet對象有梆,失敗返回nil
∫庀担可以使用lastErrorMessage
和lastErrorCode
方法來確定查詢失敗的原因泥耀。
為了迭代你的查詢的結(jié)果,可以用一個while()
循環(huán),在循環(huán)中使用next
方法來不斷取出結(jié)果.
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
//retrieve values for each record
}
即使你只希望查詢一個值也需要使用next
方法
FMResultSet * s = [db executeQuery:@“ SELECT COUNT(*)FROM myTable ” ];
if([s next ]){
int totalCount = [s intForColumnIndex:0 ];
}
FMResultSet
有許多方法可以取出數(shù)據(jù):
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumn:
-
objectForColumn:
例如,取出一個NSString
的name
數(shù)據(jù)
NSString *name = [s stringForColumn:@"name"];
每個方法還有一個對應的{type}ForColumnIndex:
變量蛔添,用來根據(jù)結(jié)果中列的位置來檢索數(shù)據(jù)痰催,而不是列名。
通常情況下沒有必要手動關(guān)閉FMResultSet
對象,因為出現(xiàn)這種情況可以當結(jié)果集被釋放迎瞧,或者數(shù)據(jù)庫被關(guān)閉自動關(guān)閉.
關(guān)閉數(shù)據(jù)庫
當你完成對數(shù)據(jù)庫的查詢和更新時夸溶,你應該close
關(guān)閉對FMDatabase
連接,這樣SQLite將釋放資源凶硅。
[db close];
事務(Transactions)
FMDatabase
可以使用begin
和commit
來包裹一個事務
多個語句的執(zhí)行
FMDatabase
可以一次執(zhí)行多個語句和用block
進行操作
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ù)
當執(zhí)行在FMDB
中執(zhí)行SQL語句,為了數(shù)據(jù)庫的安全,需要使用數(shù)據(jù)綁定語法.
INSERT INTO myTable VLUEA (?,?,?,?)
?
字符被SQLite識別為要插入的值的占位符缝裁。
NSInteger identifier = 42;
NSString *name = @"Liam O'Flaherty ";
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
),應該作為一個NSNumber
對象,通過一個語法糖@()
來完成NSInteger
到NSNumber
的轉(zhuǎn)變
同樣的SQL中的NULL
值應該使用[NSNull null]
來代替,如上面實例代碼中的comment
,使用comment ?:[NSNull null]
來代替nil
.
或者,也可以使用命名參數(shù)語法,當傳入一個字典類型的時候尤其適用:
INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)
參數(shù)必須以:
開頭
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
手動將值插入到SQL語句本身。
使用FMDatabaseQueue和線程安全
不要在多個線程中同時使用一個FMDatabase
對象足绅。當然每個線程創(chuàng)建一個FMDatabase
對象總是可以的.只是不要在線程之間共享一個實例捷绑,而且絕對不能同時在多個線程之間共享。這樣可能導致臟數(shù)據(jù)或者寫入異常.
所以不要實例化一個FMDatabase
對象并在多個線程中使用它氢妈。
正確的做法是,實例化一個FMDatabaseQueue
對象,并在不同的線程中使用這個對象,FMDatabaseQueue
會在不同的線程之間同步和協(xié)調(diào).
首先,創(chuàng)建一個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]) {
…
}
}];
一種簡單的事務的處理方法:
[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 ...
}];
注意:對FMDatabaseQueue
方法的調(diào)用是阻塞的,所以你傳遞的^block
,不會在另一個線程上運行.