美文网首页iOS精品文章-SQLiteSqliteiOS学习笔记
[iOS学习笔记]·FMDB:第三方本地数据库处理框架(官方文档

[iOS学习笔记]·FMDB:第三方本地数据库处理框架(官方文档

作者: 小码僧 | 来源:发表于2017-07-12 10:44 被阅读252次

    目前,虽然SQLite也为iOS提供了数据库操作方法,但更多的时候,一般用FMDB,正如主流APP(如QQ和微信)会用到。这里介绍一个查询主流APP主要框架的网站:AppSight

    1.使用方法(Usage)


    FMDB有三个主要的类:

    • FMDatabase:表示一个单独的SQLite数据库。 用来执行SQLite的命令。
    • FMResultSet:表示FMDatabase执行查询后结果集
    • FMDatabaseQueue:如果你想在多线程中执行多个查询或更新,你应该使用该类。这是线程安全的。

    1.1 数据库创建(Database Creation)

    创建FMDatabase对象时参数为SQLite数据库文件路径。该路径可以是以下三种之一:

    • 1.文件路径。该文件路径无需真实存,如果不存在会自动创建。
    • 2.空字符串(@"")。表示会在临时目录创建一个空的数据库,当FMDatabase 链接关闭时,文件也被删除。
    • 3.NULL. 将创建一个内在数据库。同样的,当FMDatabase连接关闭时,数据会被销毁。
    NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
    FMDatabase *db = [FMDatabase databaseWithPath:path];
    

    (如需对临时数据库或内在数据库进行一步了解,请继续阅读:http://www.sqlite.org/inmemorydb.html)

    1.2 打开数据库(Opening)

    在和数据库交互之前,数据库必须是打开的。如果资源或权限不足无法打开或创建数据库,都会导致打开失败。

    if (![db open]) {    
            [db release];   //ARC无需此行
            return;    
        }  
    

    1.3 执行更新(Executing Updates)

    一切不是SELECT命令的命令都视为更新。这包括CREATE, UPDATE, INSERT,ALTER,COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE(等)。

    简单来说,只要不是以SELECT开头的命令都是UPDATE命令。

    执行更新返回一个BOOL值。YES表示执行成功,否则表示有那些错误 。你可以调用-lastErrorMessage-lastErrorCode方法来得到更多信息。

    执行更新的方法是以-executeUpdate:开头的。

    1.4 执行查询(Executing Queries)

    SELECT命令就是查询,执行查询的方法是以-excuteQuery:开头的。

    执行查询时,如果成功返回FMResultSet对象,错误返回nil. 与执行更新相当,支持使用 NSError**参数。同时,你也可以使用-lastErrorCode-lastErrorMessage获知错误信息。

    为了遍历查询结果,你可以使用while循环。你还需要知道怎么跳到下一个记录。使用FMDB,很简单实现,就像这样:

    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:
    • dataForColumn:
    • dataNoCopyForColumn:
    • UTF8StringForColumnIndex:
    • objectForColumn:

    这些方法也都包括 {type}ForColumnIndex的这样子的方法,参数是查询结果集的列的索引位置。

    你无需调用 [FMResultSet close]来关闭结果集, 当新的结果集产生,或者其数据库关闭时,会自动关闭。

    1.5 关闭数据库(Closing)

    当使用完数据库,你应该-close 来关闭数据库连接来释放SQLite使用的资源。

    [db close];  
    

    1.6 事务(Transactions)

    FMDatabase是支持事务的。

    1.7 多重语句和批次信息(Multiple Statements and Batch Stuff)

    您可以使用FMDatabaseexecuteStatements: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;
    }];
    

    1.8 数据格式化(Data Sanitization)

    利用一个SQL语句为FMDB插入数据前,你不要尝试SQL审查(sanitize)任何值。相反的,你应该使用标准的SQLite数据绑定语法。

    INSERT INTO myTable VALUES (?, ?, ?, ?) 
    

    ?字符由SQLite识别为要插入的值的占位符。这些执行方法全部接受数量可变的参数(或这些参数的一个代表,例如NSArray,NSDictionaryva_list)。

    并且,在Objective-C中将该SQL的占位符?一起使用:

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

    注意:基本数据类型,如NSInteger变量identifier,应该是一个NSNumber对象,通过使用@如上所示的语法实现。或者您也可以使用[NSNumber numberWithInt:identifier]语法。

    同样,NULL应该插入SQL 值[NSNull null]。例如,在案件的comment,这可能是nil(而且是在这个例子中),你可以使用comment ?: [NSNull null]语法,如果将插入字符串comment不是nil,而是将插入[NSNull null]如果它是nil。

    在Swift中,您将使用它executeUpdate(values:),这不仅仅是一个简洁的Swift语法,而且也是throws错误处理正确的错误:

    do {
        let identifier = 42
        let name = "Liam O'Flaherty (\"the famous Irish author\")"
        let date = Date()
        let comment: String? = nil
    
        try db.executeUpdate("INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", values: [identifier, name, date, comment ?? NSNull()])
    } catch {
        print("error = \(error)")
    }
    

    注意:在Swift中,您不必像Objective-C那样包装基本的数字类型。但是如果要插入一个可选的字符串,你可能会使用comment ?? NSNull()语法(即,如果是nil,使用NSNull,否则使用字符串)。

    或者,您可以使用命名参数语法:

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

    参数名必须以冒名开头。SQLite本身支持其他字符,但Dictionary key的内部实现是冒号开头,所以注意你的NSDictionary 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方法stringWithFormat手动将值插入SQL语句本身。一个Swift字符串插入也不应该将值插入到SQL中。使用?占位符将值插入到数据库中(或WHERE在SELECT语句中的子句中使用)。

    1.9 补充:老版本的README

    提供给-executeUpdate:方法的参数都必须是对象。就像以下的代码就无法工作,且会产生崩溃。

    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", 42];   
    

    正确有做法是把数字打包成 NSNumber对象

    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:42]];   
    

    或者,你可以使用 -execute*WithFormat: ,这是NSString风格的参数

    [db executeUpdateWithFormat:@"INSERT INTO myTable VALUES (%d)", 42];   
    

    -execute*WithFormat: 的方法的内部实现会帮你封装数据, 以下这些修饰符都可以使用: %@, %c, %s, %d, %D,%i, %u, %U, %hi, %hu, %qi, %qu, %f, %g, %ld, %lu, %lld, and %llu。 除此之外的修饰符可能导致无法预知的结果。 一些情况下,你如果要在SQL语句中使用 % 字符,你应该使用%%

    2. 使用FMDatabaseQueue 及线程安全 (Using FMDatabaseQueue and Thread Safety)


    在多个线程中同时使用一个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 ]){ 
            ... 
        } 
    }]
    

    在transaction中封装事务的简单方法:

    [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 ... 
    }];
    

    Swift相应的版本为:

    queue.inTransaction { db, rollback in
        do {
            try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [1])
            try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [2])
            try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [3])
    
            if whoopsSomethingWrongHappened {
                rollback.pointee = true
                return
            }
    
            // etc ...
        } catch {
            rollback.pointee = true
            print(error)
        }
    }
    

    (注意,从Swift 3开始使用pointee,但在Swift 2.3中,使用memory而不是pointee。)

    FMDatabaseQueue将运行(序列化队列上的)块(因此是类名)。所以如果你同时从多个线程调用FMDatabaseQueue的方法,它们将按照它们被接收的顺序执行。这样查询和更新将不会对对方的脚趾,每一个都很开心。

    注意:对FMDatabaseQueue方法的调用是阻塞的。所以即使你正在传递块,它们也不会在另一个线程上运行。

    3. 基于块制作定制的sqlite函数(Making custom sqlite functions, based on blocks)


    你可以这样做!例如,-makeFunctionNamed:在main.m中查找

    英文原文出处:

    https://github.com/ccgus/fmdb

    相关文章

      网友评论

      本文标题:[iOS学习笔记]·FMDB:第三方本地数据库处理框架(官方文档

      本文链接:https://www.haomeiwen.com/subject/cdrrhxtx.html