iOS數據庫之FMDB的使用

iOS中原生的SQLite API在使用上相當不友好,在使用時,非常不便。于是,就出現了一系列將SQLite API進行封裝的庫,例如FMDB,PlausibleDatabase等.

安裝


使用CocoaPods來安裝FMDB

CocoaPods是Swift和Objective-C Cocoa項目的依賴管理器。它擁有4萬多個庫,用于超過280萬個應用程序。CocoaPods可以幫助您優雅地擴展您的項目。

如果你沒有創建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

然后執行

$ pod install

然后使用新生成的*.xcworkspace工程,而不是*.xcodeproj

用法


在FMDB中有三個主要的類;

  • FMDataase-表示一個SQLite數據庫.用于執行SQLite語句
  • FMResultSet-表示數據庫的查詢結果
  • FMDatabaseQueue-在多個線程來執行查詢和更新時會使用這個類

創建數據庫

需要制定一個文件路徑(path)來創建一個給予SQLite的FMDatabase,有以下三種方式可以指定文件路徑:

  • 指定一個文件路徑,如果文件不存在,則自動創建
  • 指定路徑為一個空字符串(@""),使用該方式會創建一個臨時數據庫文件,當FMDatabase被關閉的時候,臨時數據庫文件自動被刪除
  • NULL 會在內存中創建一個臨時數據庫,在FMDatabase被關閉的時候自動刪除
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];

打開數據庫

在與數據庫進行交互之前,我們需要打開數據庫,如果沒有足夠的資源或權限打開和/或創建數據庫,則打開失敗,當打開失敗的時候,把db置為nil.

if(![db open]){
db = nil;
return ;
}

執行更新

除了SELECT操作外,SQLite的所有命令都使用executeUpdate消息來執行.包括CREATE, UPDATE, INSERT, ALTER, COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM,REPLACE等,只要你不是執行SELECT語句,就是更新數據庫操作.

BOOL succ = [db executeUpdate:sql]; //sql是SQLite命令語句字符串

執行更新會返回一個布爾值。返回值YES意味著更新成功執行,返回值NO意味著遇到了一些錯誤。可以調用lastErrorMessagelastErrorCode方法來檢索更多的信息。

執行查詢

一個SELECT語句是一個查詢,并通過其中一種-executeQuery...方法執行。
如果成功執行查詢則返回一個FMResultSet對象,失敗返回nil。可以使用lastErrorMessagelastErrorCode方法來確定查詢失敗的原因。
為了迭代你的查詢的結果,可以用一個while()循環,在循環中使用next方法來不斷取出結果.

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有許多方法可以取出數據:

  • intForColumn:
  • longForColumn:
  • longLongIntForColumn:
  • boolForColumn:
  • doubleForColumn:
  • stringForColumn:
  • dateForColumn:
  • dataForColumn:
  • dataNoCopyForColumn:
  • UTF8StringForColumn:
  • objectForColumn:
    例如,取出一個NSStringname數據
NSString *name = [s stringForColumn:@"name"];

每個方法還有一個對應的{type}ForColumnIndex:變量,用來根據結果中列的位置來檢索數據,而不是列名。
通常情況下沒有必要手動關閉FMResultSet對象,因為出現這種情況可以當結果集被釋放,或者數據庫被關閉自動關閉.

關閉數據庫

當你完成對數據庫的查詢和更新時,你應該close關閉對FMDatabase連接,這樣SQLite將釋放資源。

[db close];

事務(Transactions)

FMDatabase可以使用begincommit來包裹一個事務

多個語句的執行

FMDatabase可以一次執行多個語句和用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;
}];

數據

當執行在FMDB中執行SQL語句,為了數據庫的安全,需要使用數據綁定語法.

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

注意:基本數據類型(如NSInteger),應該作為一個NSNumber對象,通過一個語法糖@()來完成NSIntegerNSNumber的轉變
同樣的SQL中的NULL值應該使用[NSNull null]來代替,如上面實例代碼中的comment,使用comment ?:[NSNull null]來代替nil.

或者,也可以使用命名參數語法,當傳入一個字典類型的時候尤其適用:

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

參數必須以:開頭

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方法stringWithFormat手動將值插入到SQL語句本身。

使用FMDatabaseQueue和線程安全

不要在多個線程中同時使用一個FMDatabase對象。當然每個線程創建一個FMDatabase對象總是可以的.只是不要在線程之間共享一個實例,而且絕對不能同時在多個線程之間共享。這樣可能導致臟數據或者寫入異常.

所以不要實例化一個FMDatabase對象并在多個線程中使用它。

正確的做法是,實例化一個FMDatabaseQueue對象,并在不同的線程中使用這個對象,FMDatabaseQueue會在不同的線程之間同步和協調.

首先,創建一個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方法的調用是阻塞的,所以你傳遞的^block,不會在另一個線程上運行.

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容