天天看點

【原】iOS學習47之第三方-FMDB

将 CocoaPods 安裝後,按照 CocoaPods 的使用說明就可以将 FMDB 第三方內建到工程中,具體請看部落格iOS學習46之第三方CocoaPods的安裝和使用(通用方法)

1. FMDB簡介

 1> 概述

  • iOS 中原生的 SQLite API 在進行資料存儲的時候,需要使用 C語言 中的函數,操作比較繁瑣。于是,就出現了一系列将SQLite API 進行封裝的庫,例如 FMDB、PlausibleDatabase、SQLitePersistentObjects 等。  
  • FMDB 是一款簡潔、易用的封裝庫。是以,在這裡推薦使用第三方架構 FMDB,它是對 libsqlite3 架構的封裝,用起來的步驟與 SQLite 使用類似,并且它對于多線程的并發操作進行了處理,是以是線程安全的。

 2> FMDB優缺點

  • 優點:

  對多線程的并發操作進行處理,是以是線程安全的;

  以OC的方式封裝了SQLite的C語言API,使用起來更加的友善;

  FMDB是輕量級的架構,使用靈活。

  • 缺點:

  因為它是OC的語言封裝的,隻能在iOS開發的時候使用,是以在實作跨平台操作的時候存在局限性。

 3> FMDB中重要的類

  • FMDatabase:一個 FMDatabase 對象就代表一個單獨的 SQLite資料庫,用來執行 SQL語句。
  • FMResultSet:使用 FMDatabase 執行查詢後的結果集。
  • FMDatabaseQueue:用于在多線程中執行多個查詢或更新,它是線程安全的。

 4> FMDB使用步驟

  • 第一步:使用 CocoaPods 将第三方內建到工程項目中
  • 第二步:導入 libsqlite3.0 架構,導入頭檔案 FMDatabase.h
  • 第三步:代碼實作,與 SQLite 使用步驟相似,建立資料庫路徑,獲得資料庫路徑,打開資料庫,然後對資料庫進行增、删、改、查操作,最後關閉資料庫。

2. FMDB建立資料庫和資料表

  • 第一步:獲得資料庫檔案的路徑

  建立FMDatabase對象時參數為SQLite資料庫檔案路徑,該路徑可以是以下三種方式之一:

   ① 檔案路徑。該檔案路徑無需真實存在,如果不存在會自動建立;

   ② 空字元串(@"")。表示會在臨時目錄建立一個空的資料庫,當 FMDatabase 連接配接關閉時,檔案也會被删除;

   ③ NULL。将建立一個内在資料庫,同樣的,當 FMDatabase 連接配接關閉時,資料将會被銷毀。

  我們一般采用第一種方式來獲得資料庫檔案的路徑,具體執行個體代碼如下:

   NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

    self.filePath = [documentPath stringByAppendingPathComponent:@"student.sqlite"];

    NSLog(@"filePath = %@", self.filePath);      
  • 第二步:使用路徑初始化FMDB對象

  使用的初始化方法:

   + (instancetype)databaseWithPath:(NSString*)aPath      

  執行個體代碼:

// 第四步:使用路徑初始化FMDB對象
    self.database = [FMDatabase databaseWithPath:self.filePath];      
  • 第三步:在和資料庫互動之前,資料庫必須是打開的。

  如果權限不足或者資源不足,則無法打開和建立資料庫。

   // 需要判斷資料庫打開的時候才進行執行語句
   if (self.database.open) {
        // 建立表
    }      
  • 第四步:建立表

  使用的執行SQL語句的方法:

- (BOOL)executeUpdate:(NSString*)sql, ...      

  該方法的傳回值是一個BOOL值,我們可以根據傳回值,來判斷SQL語句是否執行成功。

if (self.database.open) {
        // 建表語句
        NSString *createSql = @"create table if not exists t_student(id integer primary key autoincrement not null, name text not null, age integer not null, sax text not null)";
        // 執行建表語句,建立資料表
        BOOL result = [self.database executeUpdate:createSql];
        // 判斷是否建表成功
        if (result) {
            NSLog(@"創表成功");
        } else {
            NSLog(@"創表失敗");
        }
    }      
  • 第五步:關閉資料庫
// 第五步:關閉資料庫
    [self.database close];      

3. FMDB實作增、删、改、查

 1> FMDB—執行更新

  一切不是SELECT指令的指令都視為更新。這包括CREAT,UPDATE,INSERT,ALTER,BEGIN,COMMIT,DETACH,DELETE,DROP,END,EXPLAIN,VACUUM,REPLACE等。  

  簡單來說,隻要不是以 SELECT 開頭的指令都是更新指令。

      執行更新傳回一個BOOL值。YES表示執行成功,否則表示有錯誤。你可以調用 -lastErrorMessage 和 -lastErrorCode方法來得到更多資訊。

 2> 執行更新指令的相關方法

  • executeUpdate:  不确定的參數用 ? 來占位(後面參數必須是 OC 對象,;代表語句結束,也可以不寫)

  增、删、改的代碼執行個體:

// 增加(插入)資料
BOOL result = [self.database executeUpdate:@"insert into t_student(name, age, sax) values(?, ?, ?)", @"xiaoming", @12, @"男"];

// 更新資料
BOOL result = [self.database executeUpdate:@"update t_student set name = ? where name = ?", @"xiaoming", @"小明"];

// 删除資料
BOOL result = [self.database executeUpdate:@"delete from t_student where name = ?", @"xiaoming"];       
  • executeUpdateWithFormat:不确定的參數用%@,%d等來占位 (參數為原始資料類型,執行語句不區分大小寫)
// 增加(插入)資料
BOOL result = [self.database executeUpdateWithFormat:@"insert into t_student (name, age, sax) values (%@, %i, %@);", @"xiaoming", @69, @"男"];

// 更新資料
BOOL result = [self.database executeUpdateWithFormat:@"update t_student set name = %@ where name = %@", @"xiaoming", @"小明"];

// 删除資料
BOOL result = [self.database executeUpdateWithFormat:@"delete from t_student where name = %@", @"xiaoming"];      
  • executeUpdate:withArgumentsInArray:數組,直接使用數組
// 增加(插入)資料
[self.database executeUpdate:@"insert into t_student(name, age, sax) values(?, ?, ?);"  withArgumentsInArray:@[@"xiaoming", @12, @"男"]];

// 更新資料
[self.database executeUpdate:@"update t_student set name = ? where name = ?;"  withArgumentsInArray:@[@"xiaoming", @"小明"]];

// 删除資料
[self.database executeUpdate:@"delete from t_student where name = ?;"  withArgumentsInArray:@[@"xiaoming"]];      

  以上的方法大家可以根據自己的習慣和需求選擇一種即可。

 3> FMDB—查詢資料

  ① 概述  

  SELECT 指令就是查詢,執行查詢的方法是以 -excuteQuery 開頭的。

  執行查詢時,如果成功傳回 FMResultSet 對象,錯誤傳回 nil 。與執行更新相同,支援使用 NSError 參數。

  同時,你也可以使用 -lastErrorCode 和 -lastErrorMessage 獲知錯誤資訊。

  ② FMResultSet

  FMResultSet 提供了很多方法,來擷取對應字段的資訊:  

    intForColumn:、longForColumn:、longLongIntForColumn:、boolForColumn:、doubleForColumn:、stringForColumn:、dataForColumn:、dataNoCopyForColumn:、UTF8StringForColumnIndex:、objectForColumn:

  ③ 執行查詢語句

  查詢整個表

// 查詢結果使用的類FMResultSet
    FMResultSet *resultSet = [self.database executeQuery:@"select * from t_student"];      

  根據條件查詢

//根據條件查詢
    FMResultSet *resultSet = [self.db executeQuery:@"select * from t_student where id<?;", @14];      

  ④ 周遊結果集合

while (resultSet.next) {
        NSInteger ID = [resultSet intForColumn:@"id"];
        NSString *name = [resultSet objectForColumnName:@"name"];
        NSInteger age = [resultSet intForColumn:@"age"];
        NSString *sax = [resultSet objectForColumnName:@"sax"];
        
        NSLog(@"id = %ld name = %@, age = %ld, sax = %@", ID, name, age, sax);
    }      

 4> 完整執行個體代碼

#import "ViewController.h"

// 第一步:引入架構,引入支援的類庫(libsqlite3.0.tbd)
#import <FMDB.h>

@interface ViewController ()

/// 聲明資料庫對象
@property (nonatomic, strong) FMDatabase *database;

/// 聲明存儲路徑
@property (nonatomic, strong) NSString *filePath;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self createTabe];
}

#pragma mark - 建立表
- (void)createTabe
{
    // 第一步:建立sql語句
    NSString *createSql = @"create table if not exists t_student(id integer primary key autoincrement not null, name text not null, age integer not null, sax text not null)";
    
    // 第二步:找到存儲路徑
    NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//    NSLog(@"document = %@", documentPath);
    self.filePath = [documentPath stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"filePath = %@", self.filePath);
    
    // 第三步:使用路徑初始化FMDB對象
    self.database = [FMDatabase databaseWithPath:self.filePath];
    
    // 第四步:資料庫執行相關的操作
    // 需要判斷資料庫打開的時候才進行執行語句
    if (self.database.open) {
        BOOL result = [self.database executeUpdate:createSql];
        if (result) {
            NSLog(@"創表成功");
        } else {
            NSLog(@"創表失敗");
        }
    }
    
    // 第五步:關閉資料庫
    [self.database close];
}

#pragma mark - 插入
- (IBAction)insertIntoAction:(id)sender
{
    // 第一步:打開資料庫
    [self.database open];
    
    // 第二步:進行相關的操作
    NSArray *nameArray = @[@"MBBoy", @"BoomSky", @"小明"];
    
    for (NSString *name in nameArray) {

        BOOL result = [self.database executeUpdate:@"insert into t_student(name, age, sax) values(?, ?, ?)", name, @69, @"男"]; // integer的資料不能在這裡使用,必須使用一個對象型資料,比如NSNumber、NSString...
        
        [self.database executeUpdate:@"INSERT INTO t_student(name, age, sax) VALUES  (?, ?, ?);"  withArgumentsInArray:@[@"xiaoming", @12, @"男"]];
        
        if (result) {
            NSLog(@"插入成功");
        } else {
            NSLog(@"插入失敗");
        }
    }
    [self.database close];// 更新資料// 删除資料// 增加(插入)資料
}

#pragma mark - 更新
- (IBAction)updateAction:(id)sender
{
    [self.database open];
    
    BOOL result = [self.database executeUpdate:@"update t_student set name = ? where name = ?", @"xiaoming", @"小明"];
    
    if (result) {
        NSLog(@"更新成功");
    } else {
        NSLog(@"更新失敗");
    }
    
    [self.database close];
}

#pragma mark - 删除
- (IBAction)deleteAction:(id)sender
{
    [self.database open];
    BOOL result = [self.database executeUpdate:@"delete from t_student where name = ?", @"MBBoy"];
    if (result) {
        NSLog(@"删除成功");
    } else {
        NSLog(@"删除失敗");
    }
    [self.database close];
}

#pragma mark - 查詢
- (IBAction)selectAction:(id)sender
{
    [self.database open];
    
    // 查詢結果使用的類FMResultSet
    FMResultSet *resultSet = [self.database executeQuery:@"select * from t_student"];
    
    // 周遊出需要的結果内容
    while (resultSet.next) {
        NSInteger ID = [resultSet intForColumn:@"id"];
        NSString *name = [resultSet objectForColumnName:@"name"];
        NSInteger age = [resultSet intForColumn:@"age"];
        NSString *sax = [resultSet objectForColumnName:@"sax"];
        
        NSLog(@"id = %ld name = %@, age = %ld, sax = %@", ID, name, age, sax);
    }
    
    [self.database close];
}

@end      

4. FMDB實作多線程操作

  • 如果應用中使用了多線程操作資料庫,那麼就需要使用 FMDatabaseQueue 來保證線程安全了。 應用中不可在多個線程中共同使用一個 FMDatabase 對象操作資料庫,這樣會引起資料庫資料混亂(例如使用兩個線程同時對資料庫進行更新和查找)。
  • 多個線程更新相同的資源導緻資料競争時使用等待隊列(等待現在執行的處理結束)。
  • 以隊列的形式添加是 FMDB 比較常用的添加方式。
  • FMDB 不支援多個線程同時操作,一般使用串行方式實作相關的操作。

 2> 建立操作隊列

  使用的初始化方法

+ (instancetype)databaseQueueWithPath:(NSString*)aPath      

 3> 把操作打包放在操作隊列中

  打包的方法

- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block      

  在 Block 中添加串行隊列

 4> 執行個體代碼

#pragma mark - 以隊列的形式添加很多學生
- (IBAction)insertManyStudent:(id)sender
{
    // 以隊列的形式添加學生是FMDB比較常用的添加方式
    // FMDB不支援多個線程同時操作,一般使用串行方式實作相關的操作
    
    [self.database open];
    
    // 第一步:建立操作隊列
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:self.filePath];
    
    // 辨別:記錄是否操作成功
    __block BOOL isSucceed = YES;
    
    // 第二步:把操作打包放在操作隊列中
    NSString *insertSql = @"insert into t_studen(name, age, sax) values(?, ?, ?)";
    
    [queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        // 串行隊列
        isSucceed &= [db executeUpdate:insertSql, @"隔壁老王", @38, @"男"];
        isSucceed &= [db executeUpdate:insertSql, @"Black", @18, @"女"];
        isSucceed &= [db executeUpdate:insertSql, @"-1", @23, @"男"];
        
        if (!isSucceed) {
            
            // block 傳回的參數rollback進行處理(BOOL類型的指針)
            *rollback = YES;
            return;
        } else {
            NSLog(@"以隊列的形式添加成功");
        }
    }];
    
    [self.database close];
}