iOS开发源码阅读篇--FMDB源码分析2(FMResultSet)

1、前言

如上一章所讲,FMDB源码主要有如下几个文件组成:面试

FMResultSet : 表示FMDatabase执行查询以后的结果集。sql

FMDatabase : 表示一个单独的SQLite数据库操做实例,经过它能够对数据库进行增删改查等等操做。数据库

FMDatabaseAdditions : 扩展FMDatabase类,新增对查询结果只返回单个值的方法进行简化,对表、列是否存在,版本号,校验SQL等等功能。数组

FMDatabaseQueue : 使用串行队列 ,对多线程的操做进行了支持。缓存

FMDatabasePool : 使用任务池的形式,对多线程的操做提供支持。(不过官方对这种方式并不推荐使用,优先选择FMDatabaseQueue的方式:ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD)bash

这一篇咱们就来说讲FMDatabase和FMDatabaseAdditions的实现思路。网络

这是一个个人iOS交流群:624212887,群文件自行下载,无论你是小白仍是大牛热烈欢迎进群 ,分享面试经验,讨论技术, 你们一块儿交流学习成长!但愿帮助开发者少走弯路。——点击:加入多线程

2、FMDatabase源码分析

2.1:打开数据库链接

-(BOOL)open;实际上是对sqlite3_open()函数的封装。函数

- (BOOL)open {
    if (_db) {
        return YES;
    }
    int err = sqlite3_open([self sqlitePath], (sqlite3**)&_db );
    if(err != SQLITE_OK) {
        NSLog(@"error opening!: %d", err);
        return NO;
    }
    //当执行这段代码的时候,数据库正在被其余线程访问,那咱们就须要给他设置一个重试时间,默认为2秒。
    if (_maxBusyRetryTimeInterval > 0.0) {
        // set the handler
        [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
    } 
    return YES;
}

- (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout {
    _maxBusyRetryTimeInterval = timeout;
    if (!_db) {
        return;
    }
    if (timeout > 0) {
        sqlite3_busy_handler(_db, &FMDBDatabaseBusyHandler, (__bridge void *)(self));
    }
    else {
        // turn it off otherwise
        sqlite3_busy_handler(_db, nil, nil);
    }
}
复制代码

setMaxBusyRetryTimeInterval设置重试时间,其实调用的是源码分析

int sqlite3_busy_handler(sqlite3

,int(
)(void
,int),void
);

该函数的第一个参数为:须要告知哪个数据库须要设置busy handler。

第二个参数是须要回调的busy handler,当你调用该回调函数的时候,须要传递给它一个void*的参数拷贝,也就是sqlite3_busy_handler的第三个参数。

另外一个须要传给回调函数的int参数表示此次锁事件,该回调函数被调用的次数。

若是回调函数返回0时,将再也不尝试再次访问数据库而返回SQLITE_BUSY或者SQLITE_IOERR_BLOCKED.若是回调函数返回非0,将会不断尝试操做数据困。

程序运行过程当中,若是有其余进程或者线程在读写数据库,那么sqlite3_busy_handler会不断调用该回调函数,直到其余线程或者进程释放锁。得到锁以后,不会再调用该回调函数,从而继续向下执行下去,进行数据库操做。该函数是在获取不到锁的时候,以执行回调函数的次数来进行延时,等待其余进程或者线程操做数据库结束,从而得到锁进行操做数据库。

2.2:查询数据库

executeQuery系列函数从根本上看其实调用的都是

-(FMResultSet )executeQuery:(NSString )sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args。

参数sql : 须要查询的sql语句。

参数arrayArgs : 数组类型的参数。应于于

FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?" withArgumentsInArray:@[@25]];
复制代码

参数dictionaryArgs:字典类型的参数。应用于

FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > :age" withParameterDictionary:@{@"age":@25}];
复制代码

参数args:可变参数类型。应用于

FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?",@(20)];
复制代码

下面来看一下该函数的大体实现过程,主要就是对sqlite3_prepare_v2的封装。

- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {

    if (![self databaseExists]) {//判断数据库是否存在
        return 0x00;
    }

    if (_isExecutingStatement) {//判断数据库是否已经在使用当中
        [self warnInUse];
        return 0x00;
    }

    _isExecutingStatement = YES;

    int rc                  = 0x00;
    sqlite3_stmt *pStmt     = 0x00;
    FMStatement *statement  = 0x00;
    FMResultSet *rs         = 0x00;

    if (_traceExecution && sql) {//打印sql语句
        NSLog(@"%@ executeQuery: %@", self, sql);
    }

    if (_shouldCacheStatements) {//获取缓存数据
        statement = [self cachedStatementForQuery:sql];
        pStmt = statement ? [statement statement] : 0x00;
        [statement reset];
    }

    if (!pStmt) {//没有缓存数据,直接查询数据库

        rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);//对sql语句进行预处理,生成预处理过的“sql语句”pStmt。

        if (SQLITE_OK != rc) {//出错处理
            if (_logsErrors) {
                NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
                NSLog(@"DB Query: %@", sql);
                NSLog(@"DB Path: %@", _databasePath);
            }

            if (_crashOnErrors) {
                NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
                abort();
            }

            sqlite3_finalize(pStmt);
            _isExecutingStatement = NO;
            return nil;
        }
    }

    id obj;
    int idx = 0;
    int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)//queryCount获取参数个数,“?”或者“:age”都算。

    // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support if (dictionaryArgs) {//对dictionaryArgs参数的处理,相似于":age"参数形式 for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { // Prefix the key with a colon. NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; if (_traceExecution) { NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]); } // Get the index for the parameter name. int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); FMDBRelease(parameterName); if (namedIdx > 0) { // Standard binding from here. // * 将通配符?:age按照索引 赋值为obj。 //* SELECT * FROM t_student WHERE age > :age --> SELECT * FROM t_student WHERE age > obj [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]; // increment the binding count, so our check below works out idx++; } else { NSLog(@"Could not find index for %@", dictionaryKey); } } } else {//对于arrayArgs参数和不定参数的处理,相似于"?"参数形式 while (idx < queryCount) { if (arrayArgs && idx < (int)[arrayArgs count]) {//数组形式 obj = [arrayArgs objectAtIndex:(NSUInteger)idx]; } else if (args) {//不定参数形式 obj = va_arg(args, id); } else { //We ran out of arguments break; } if (_traceExecution) { if ([obj isKindOfClass:[NSData class]]) { NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]); } else { NSLog(@"obj: %@", obj); } } idx++; // * 将通配符?:age按照索引 赋值为obj。 //* SELECT * FROM t_student WHERE age > ? --> SELECT * FROM t_student WHERE age > obj [self bindObject:obj toColumn:idx inStatement:pStmt];//绑定参数值 } } if (idx != queryCount) {//若是绑定的参数数目不对,则进行出错处理 NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)"); sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } FMDBRetain(statement); // to balance the release below if (!statement) {//生成FMStatement对象 statement = [[FMStatement alloc] init]; [statement setStatement:pStmt]; if (_shouldCacheStatements && sql) {//缓存处理,key为sql语句,值为statement [self setCachedStatement:statement forQuery:sql]; } } // the statement gets closed in rs's dealloc or [rs close];
    rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];//生成FMResultSet结果集对象
    [rs setQuery:sql];

    NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
    [_openResultSets addObject:openResultSet];

    [statement setUseCount:[statement useCount] + 1];

    FMDBRelease(statement);

    _isExecutingStatement = NO;

    return rs;
}
复制代码

上面有一个关键的函数就是- (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt进行参数绑定,把通配符形式的参数改为obj实际参数。

将通配符?:age按照索引 赋值为obj。好比:SELECT * FROM t_student WHERE age > :age –> SELECT * FROM t_student WHERE age > obj 或者 SELECT * FROM t_student WHERE age > ? –> SELECT * FROM t_student WHERE age > obj

2.3 更新数据库操做

这里的更新,并不仅是单单的更新数据。而是对数据库有更改的操做,增删改都算。FMDB调用的都是executeUpdate系列函数。这个函数基本上跟executeQuery系列函数的实现基本差很少。只是它生成statement对象后,直接调用rc = sqlite3_step(pStmt);更新执行,而没有像executeQuery延迟到FMResultSet中的next函数中执行。

2.4 WithFormat形式的数据查询和更新

好比:FMResultSet resultSet = [_db executeQueryWithFormat:@”SELECT FROM t_student WHERE age > %d”,20];替换原来?通配符。

- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... {
    va_list args;
    va_start(args, format);
    NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]];
    NSMutableArray *arguments = [NSMutableArray array];
    [self extractSQL:format argumentsList:args intoString:sql arguments:arguments];
    va_end(args);
    return [self executeQuery:sql withArgumentsInArray:arguments];
}
复制代码

从代码中能够看出,这里实际上是使用

/**
 *  将format的sql语句 改成 可用的sql语句
 *  SELECT * FROM t_student WHERE age > %d --> SELECT * FROM t_student WHERE age > ?
 *
 *  @param sql        SELECT * FROM t_student WHERE age > %d
 *  @param args       可变参数,c语言的格式化参数
 *  @param cleanedSQL SELECT * FROM t_student WHERE age > ?
 *  @param arguments  oc数组
 */
- (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments;
复制代码

2.5 一次性执行多条sql语句。

使用executeStatements函数能够一次性执行多条sql语句,实例以下:

/**
 *  将多个SQL执行语句写入一个字符串中,并执行
 */
- (void)executeStatementsTest{
    NSString *sql =
    @"CREATE TABLE IF NOT EXISTS t_student_tmp (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"
    "INSERT INTO t_student_tmp (name, age) VALUES ('yixiang', 10);"
    "INSERT INTO t_student_tmp (name, age) VALUES ('yixiangXX', 20);";
    BOOL success = [_db executeStatements:sql];
    if (success) {
        NSLog(@"执行成功");
    }else{
        NSLog(@"执行失败");
    }

    sql = @"SELECT * FROM t_student;"
          "SELECT * FROM t_student_tmp;";
    success = [_db executeStatements:sql withResultBlock:^int(NSDictionary *resultsDictionary) {
        NSLog(@"%@",resultsDictionary);//查询结果都在resultsDictionary
        return 0;
    }];
    if (success) {
        NSLog(@"查询成功");
    }else{
        NSLog(@"查询失败");
    }

}
复制代码

它又是如何实现的呢?他其实就是对sqlite3_exec函数的封装。

- (BOOL)executeStatements:(NSString *)sql withResultBlock:(FMDBExecuteStatementsCallbackBlock)block {

    int rc;
    char *errmsg = nil;

    rc = sqlite3_exec([self sqliteHandle], [sql UTF8String], block ? FMDBExecuteBulkSQLCallback : nil, (__bridge void *)(block), &errmsg);

    if (errmsg && [self logsErrors]) {
        NSLog(@"Error inserting batch: %s", errmsg);
        sqlite3_free(errmsg);
    }

    return (rc == SQLITE_OK);
}
复制代码

2.6:FMDB的加解密

FMDB中使用- [FMDatabase setKey:]和- [FMDatabase setKeyWithData:]输入数据库密码以求验证用户身份,使用- [FMDatabase rekey:]和- [FMDatabase rekeyWithData:]来给数据库设置密码或者清除密码。这两类函数分别对sqlite3_key和sqlite3_rekey函数进行了封装。

3、FMDatabaseAdditions源码分析

扩展FMDatabase类,新增对查询结果只返回单个值的方法进行简化,对表、列是否存在,版本号,校验SQL等等功能。

3.1:XXXForQuery系列函数

对查询结果只有一个值得状况进行优化,有多个值也只取第一个值。用法实例:

/**
 *  使用FMDatabaseAdditions中的intForQuery函数查找数据,若是返回结果有多个数据只取第一条数据
 */
- (void)queryForIntForQuery{
    int idx = [_db intForQuery:@"SELECT id FROM t_student WHERE age = ?",@(26)];
    NSLog(@"%zi",idx);
}
复制代码

3.2: 数据库的一些概要信息

-(BOOL)tableExists:(NSString*)tableName;数据库表是否存在。

-(BOOL)columnExists:(NSString

)columnName inTableWithName:(NSString
)tableName;在tableName表中columnName是否存在。

-(FMResultSet*)getSchema;数据库的一些概要信息。实例以下:

/**
 *schema概要信息
 */
- (void)querySchema{
    //查询数据库schema(全部表的一些信息)
    //*对于表来讲, tbl_name 则仍然是表名。 但sqlite_master 中不只是表记录,还有其它的对象,好比索引,对于索引来讲 name 是索引的名字,而tbl_name 是索引所属的表名。
    FMResultSet *resultSet = [_db getSchema];
    while ([resultSet next]) {
        NSString *type = [resultSet stringForColumn:@"type"];
        NSString *name = [resultSet stringForColumn:@"name"];
        NSString *tbl_name = [resultSet stringForColumn:@"tbl_name"];
        int  rootpage = [resultSet intForColumn:@"rootpage"];
        NSString *sql = [resultSet stringForColumn:@"sql"];

        NSLog(@"\n %@ \n %@ \n %@ \n %zi \n %@",type,name,tbl_name,rootpage,sql);
    }

    /**
     *  每一张表具体的概要信息(也就是每一列的信息)
     *  get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER]
     */
    resultSet = [_db getTableSchema:@"t_student"];
    while ([resultSet next]) {
        int cid = [resultSet intForColumn:@"cid"];
        NSString *name = [resultSet stringForColumn:@"name"];
        NSString *type = [resultSet stringForColumn:@"type"];
        int notnull = [resultSet intForColumn:@"notnull"];
        int pk = [resultSet intForColumn:@"pk"];
        NSLog(@"\n %zi \n %@ \n %@ \n %zi \n %zi",cid,name,type,notnull,pk);
    }
}
复制代码

3.3:校验sql语句是否合法

-(BOOL)validateSQL:(NSString

)sql error:(NSError
*)error;

4、联系方式

这是一个个人iOS交流群:624212887,群文件自行下载,无论你是小白仍是大牛热烈欢迎进群 ,分享面试经验,讨论技术, 你们一块儿交流学习成长!但愿帮助开发者少走弯路。——点击:加入

若是以为对你还有些用,就关注小编+喜欢这一篇文章。你的支持是我继续的动力。

下篇文章预告:·FMDB源码分析3(FMDatabase+FMDatabaseAdditions)

文章来源于网络,若有侵权,请联系小编删除。

相关文章
相关标签/搜索