FMDB的使用方法

2017/11/17 posted in  iOS
  • 总结

    最近再看其它大牛写的项目代码,发现许多用到了FMDB,所以去了解了一下。

    FMDB简介

    FMDB是一个第三方的开源库,我们可以通过cocopods搜索并整合到项目里面,FMDB其实就是对SQLiteAPI进行了封装,加上了面向对象的思想,让我们不必使用繁琐的C语言API函数,比起直接操作SQLite更加方便。

    并且FMDB 同时兼容 ARC 和非 ARC 工程,会自动根据工程配置来调整相关的内存管理代码。

    使用方法

    本文使用方法,均参考FMDBgithub项目文档https://github.com/ccgus/fmdb

    引入相关文件

    因为是对sqlite的封装所以我们在项目中需要引入它的库。

    之后在文件中导入它的头文件:

    #import "FMDB.h"
    

    建立数据库

    建立数据库只有简单的一句代码,如果当前路径不存在所需的数据库则会自动创建,若存在则会获取到。当路径为字符(@“”)时,一个空的数据库将被创建在临时的位置,数据库关闭时候将被自动删除。路径为NULL时空数据库会被放在内存中,关闭时也将自动被删除。具体信息可以参见:http://www.sqlite.org/inmemorydb.html

    #define PATH_OF_DOCUMENT    [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
    
    _path = [PATH_OF_DOCUMENT stringByAppendingPathComponent:@"test.db"];
    //创建数据库
    FMDatabase *db = [FMDatabase databaseWithPath:_path];
    

    打开数据库

    在对数据库进行交互时,必须要先打开它。如果打开失败,可能是权限不足或者资源不足。

    if (![db open]) {
        // [db release];   // uncomment this line in manual referencing code; in ARC, this is not necessary/permitted
        db = nil;
        return;
    }
    

    执行更新(update)操作

    FMDB中除了select为查询(query)以为都为更新操作。

    例如我们执行一个插入操作的完整步骤为:

        static int idx = 1;
        FMDatabase *db = [FMDatabase databaseWithPath:_path];
        if ([db open]) {
            NSString * sql = @"insert into User (name, password) values(?, ?) ";
            NSString *name = [NSString stringWithFormat:@"lzh%d",idx++];
            BOOL result = [db executeUpdate:sql,name,@"op"];
            if (!result) {
                debugLog(@"error to insert data");
            }else{
                debugLog(@"succ to insert data");
            }
            [db close];
        }
    

    查询操作:

        FMDatabase *db = [FMDatabase databaseWithPath:_path];
        if ([db open]) {
            NSString *sql =@"select * from User";
            FMResultSet *result = [db executeQuery:sql];
            while ([result next]) {
                int userId = [result intForColumn:@"id"];
                NSString *name = [result stringForColumn:@"name"];
                NSString *pass = [result stringForColumn:@"password"];
                debugLog(@"user id = %d, name = %@, pass = %@", userId, name, pass);
            }
            [db close];
        }
    

    删除操作:

        static int idx = 1;
        FMDatabase *db =[FMDatabase databaseWithPath:_path];
        if ([db open]) {
            NSString *sql = @"delete from User where id = ?";
            BOOL result = [db executeUpdate:sql , @(idx++)];
            if (!result) {
                debugLog(@"error to delete db data");
            } else {
                debugLog(@"succ to deleta db data");
            }
            [db close];
        }
    

    我们可以看到执行sql语句的时候用的都是executeUpdate:方法。

    执行查询操作

    查询操作与上面的有点区别,我们需要用FMResultSet来存储我们的查询结果,并调用它的next:方法来对数据进行逐行操作:

        FMDatabase *db = [FMDatabase databaseWithPath:_path];
        if ([db open]) {
            NSString *sql =@"select * from User";
            FMResultSet *result = [db executeQuery:sql];
            while ([result next]) {
                int userId = [result intForColumn:@"id"];
                NSString *name = [result stringForColumn:@"name"];
                NSString *pass = [result stringForColumn:@"password"];
                debugLog(@"user id = %d, name = %@, pass = %@", userId, name, pass);
            }
            [db close];
        }
    

    上面代码可以发现执行sql语句变为executeQuery:方法,该方法会将结果返回为FMResultSet类型,之后我们需要调用stringForColumn:对结果进行解析。
    FMDB提供如下多个方法来获取不同类型的数据:

    intForColumn:
    longForColumn:
    longLongIntForColumn:
    boolForColumn:
    doubleForColumn:
    stringForColumn:
    dateForColumn:
    dataForColumn:
    dataNoCopyForColumn:
    UTF8StringForColumn:
    objectForColumn:
    

    也可以按照列的索引对数据进行获取,{type}ForColumnIndex:

    数据参数

    我们可以在sql语句中,用表示执行语句的参数,然后在 executeUpdate:方法来将?所指代的具体参数传入,例如上面的代码:

        NSString * sql = @"insert into User (name, password) values(?, ?) ";
        NSString *name = [NSString stringWithFormat:@"lzh%d",idx++];
        BOOL result = [db executeUpdate:sql,name,@"op"];
    

    线程安全

    FMDatabase这个类是线程不安全的,如果在多个线程同时使用一个FMDatabase实例,会造成数据混乱问题。所以,提供了一个FMDatabaseQueue并且使用它来对多个线程间进行交互,FMDatabaseQueue对象将通过接入多个线程进行同步和整合。

    使用的方法也很简单:

    首先创建一个数据库path来初始化FMDatabaseQueue,然后就可以将一个闭包 (block) 传入 inDatabase 方法中。

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

    按照上面的方法我们可以创建多个线程来异步的对数据库进行操作:

    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:_path];
        dispatch_queue_t q1 = dispatch_queue_create("queue1", NULL);
        dispatch_queue_t q2 = dispatch_queue_create("queue2", NULL);
        
        dispatch_async(q1, ^{
            for (int i =1; i<100; ++i) {
                [queue inDatabase:^(FMDatabase *db){
                    NSString *sql = @"insert into User (name, password) values(?, ?)";
                    NSString *name = [NSString stringWithFormat:@"queue1 %d", i];
                    BOOL result = [db executeUpdate:sql,name,@"opop"];
                    if (!result) {
                        debugLog(@"error to add db data: %@", name);
                    } else {
                        debugLog(@"succ to add db data: %@", name);
                    }
                }];
            }
        });
        dispatch_async(q2,^{
            for (int i = 0; i < 100; ++i) {
                [queue inDatabase:^(FMDatabase *db) {
                    NSString * sql = @"insert into user (name, password) values(?, ?) ";
                    NSString * name = [NSString stringWithFormat:@"queue2 %d", i];
                    BOOL result = [db executeUpdate:sql, name, @"opop22"];
                    if (!result) {
                        debugLog(@"error to add db data: %@", name);
                    } else {
                        debugLog(@"succ to add db data: %@", name);
                    }
                }];
            }
        });
    

    执行后可以发现数据库中的部分表数据如下:

    两个线程可以异步执行互不干扰。

    上面数据库的显示 使用的是Navicat,也有其它的数据库管理软件可以显示。

    总结

    FMDB是一个在iOS上简化sqlite API的第三方库,对sqlite进行了很有好的封装,便于维护与增加效率。