zoukankan      html  css  js  c++  java
  • FMDB源码解析

    上一篇博客讲述SQLite的使用,本篇将讲述FMDB源码,后面也会讲解SQLite在使用与FMDB的区别。本篇读下来大约20-30分钟,建议大家先收藏一下。

    FMDB是以OC方式封装SQLite中C语言的API,也是iOS中SQLite数据库的框架,在目前研发项目中使用的也是比较广泛的。下面直入正题

    一、FMDB源码结构

    首先我们来看一下FMDB的源码的结构与组成,如下图:

    我们可以从结构上看出FMDB在共有5个文件组成,其中FMDB.h用于管理其他5个文件,下面分别讲述5个文件的用处

    (1)FMDatabase:代表一个单独的SQLite操作实例,数据库通过它增删改查操作;

    (2)FMResultSet:代表查询后的结果集;

    (3)FMDatabaseQueue:代表串行队列,对多线程操作提供了支持;

    (4)FMDatabaseAdditions:本类用于扩展FMDatabase,用于查找表是否存在,版本号等功能;

    (5)FMDatabasePool:此方式官方是不推荐使用,代表是任务池,也是对多线程提供了支持。

    下面将具体讲述每一个类的核心代码是怎么样的?

    二、FMDB源码解析

    2.1 FMDatabase源码解析

    2.1.1 打开数据库连接

    - (BOOL)open;是对SQLite中sqlite3_open()函数的封装使用

    下面看一下具体使用

    - (BOOL)open {
        if (_isOpen) {
            return YES;
        }
        if (_db) {
            [self close];
        }
        // now open database
        int err = sqlite3_open([self sqlitePath], (sqlite3**)&_db );
        if(err != SQLITE_OK) {
            NSLog(@"error opening!: %d", err);
            return NO;
        }
        //当执行这段代码的时候,数据库正在被其他线程访问,就需要给他设置重试时间,
        if (_maxBusyRetryTimeInterval > 0.0) {
            // set the handler
            
            [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
        }
        
        _isOpen = YES;
        
        return YES;
    }
    
    //下面我们看一下setMaxBusyRetryTimeInterval的实现方法
    - (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()设置重试时间,相当于SQLite中调用int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);

     针对int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);该函数

    (1)第一个参数:哪个数据库需要设置busy_handler

    (2)第二个参数:需要回调的busy handler,调用次函数时,需要传参,是sqlite3_busy_handler第三个参数

    (3)第三个参数:int参数代表锁事件,该函数被调用次数,如果返回为0,不会再次访问数据库,返回非0,将不断尝试访问数据库。

    当获取不到锁时,会执行回调函数的次数以此来延时,等待其他线程等操作完数据库,这样获得操作数据库。

    2.1.2 查询数据库

    executeQuery函数是数据库比较重要的方法。

    在看实现文件

    - (FMResultSet *)executeQuery:(NSString*)sql, ... {
        va_list args;
        va_start(args, sql);
        //整个方法关键是下面一句
        id result = [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args];
        
        va_end(args);
        return result;
    }

    从上面可以看出:调用executeQuery函数,实际上是调用

    - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args函数,下面看下此函数的实现方式。

    - (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;
        //打印sql语句
        if (_traceExecution && sql) {
            NSLog(@"%@ executeQuery: %@", self, sql);
        }
        
        //获取缓存数据
        if (_shouldCacheStatements) {
            statement = [self cachedStatementForQuery:sql];
            pStmt = statement ? [statement statement] : 0x00;
            [statement reset];
        }
        
        //没有缓存数据,直接查询数据库
        if (!pStmt) {
            //对sql语句进行预处理,生成预处理过的“sql语句”pStmt。
            rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
            
            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!)
        
        // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
        //对dictionaryArgs参数的处理,类似于下面":age"参数形式
        if (dictionaryArgs) {
            
            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.
                    [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++;
                
                [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];
            //缓存的处理,key为sql语句,值为statement
            if (_shouldCacheStatements && sql) {
                [self setCachedStatement:statement forQuery:sql];
            }
        }
        
        // the statement gets closed in rs's dealloc or [rs close];
        rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
        [rs setQuery:sql];
        
        NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
        [_openResultSets addObject:openResultSet];
        
        [statement setUseCount:[statement useCount] + 1];
        
        FMDBRelease(statement);
        
        _isExecutingStatement = NO;
        
        return rs;
    }

    发现上面那个函数有四个参数,看到源码之后,我们一一讲述四个参数:

    (1)第一个参数sql:代表我们要查询的sql语句;

    (2)第二个参数arrayArgs:代表数组类型的参数,举例如下:

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

    (3)第三个参数dictionaryArgs:代表字典类型的参数,举例如下:

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

    (4)第四个参数args:代表可变类型的参数,举例如下:

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

    上面是查询基本源码讲解,大家可以详细里面的源码实现,里面也有讲解。

    2.1.3 更新数据库

    针对FMDB数据库增删改都属于对数据库的更新操作,FMDB通过executeUpdate系列函数来实现对数据库的更新操作。

    - (BOOL)executeUpdate:(NSString*)sql, ...;系列函数,我们来看一下executeUpdate函数的实现。

    - (BOOL)executeUpdate:(NSString*)sql, ... {
        va_list args;
        va_start(args, sql);
        //主要是下面这个函数
        BOOL result = [self executeUpdate:sql error:nil withArgumentsInArray:nil orDictionary:nil orVAList:args];
        
        va_end(args);
        return result;
    }

    我们再来看一下红色标出的代码实现,- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args函数实现。

    #pragma mark Execute updates
    
    - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
        
        if (![self databaseExists]) {
            return NO;
        }
        
        if (_isExecutingStatement) {
            [self warnInUse];
            return NO;
        }
        
        _isExecutingStatement = YES;
        
        int rc                   = 0x00;
        sqlite3_stmt *pStmt      = 0x00;
        FMStatement *cachedStmt  = 0x00;
        
        if (_traceExecution && sql) {
            NSLog(@"%@ executeUpdate: %@", self, sql);
        }
        
        if (_shouldCacheStatements) {
            cachedStmt = [self cachedStatementForQuery:sql];
            pStmt = cachedStmt ? [cachedStmt statement] : 0x00;
            [cachedStmt reset];
        }
        
        if (!pStmt) {
            rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
            
            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();
                }
                
                if (outErr) {
                    *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
                }
                
                sqlite3_finalize(pStmt);
                
                _isExecutingStatement = NO;
                return NO;
            }
        }
        
        id obj;
        int idx = 0;
        int queryCount = sqlite3_bind_parameter_count(pStmt);
        
        // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
        if (dictionaryArgs) {
            
            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.
                    [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
                    
                    // increment the binding count, so our check below works out
                    idx++;
                }
                else {
                    NSString *message = [NSString stringWithFormat:@"Could not find index for %@", dictionaryKey];
                    
                    if (_logsErrors) {
                        NSLog(@"%@", message);
                    }
                    if (outErr) {
                        *outErr = [self errorWithMessage:message];
                    }
                }
            }
        }
        else {
            
            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++;
                
                [self bindObject:obj toColumn:idx inStatement:pStmt];
            }
        }
        
        
        if (idx != queryCount) {
            NSString *message = [NSString stringWithFormat:@"Error: the bind count (%d) is not correct for the # of variables in the query (%d) (%@) (executeUpdate)", idx, queryCount, sql];
            if (_logsErrors) {
                NSLog(@"%@", message);
            }
            if (outErr) {
                *outErr = [self errorWithMessage:message];
            }
            
            sqlite3_finalize(pStmt);
            _isExecutingStatement = NO;
            return NO;
        }
        
        /* Call sqlite3_step() to run the virtual machine. Since the SQL being
         ** executed is not a SELECT statement, we assume no data will be returned.
         */
        
        rc      = sqlite3_step(pStmt);
        
        if (SQLITE_DONE == rc) {
            // all is well, let's return.
        }
        else if (SQLITE_INTERRUPT == rc) {
            if (_logsErrors) {
                NSLog(@"Error calling sqlite3_step. Query was interrupted (%d: %s) SQLITE_INTERRUPT", rc, sqlite3_errmsg(_db));
                NSLog(@"DB Query: %@", sql);
            }
        }
        else if (rc == SQLITE_ROW) {
            NSString *message = [NSString stringWithFormat:@"A executeUpdate is being called with a query string '%@'", sql];
            if (_logsErrors) {
                NSLog(@"%@", message);
                NSLog(@"DB Query: %@", sql);
            }
            if (outErr) {
                *outErr = [self errorWithMessage:message];
            }
        }
        else {
            if (outErr) {
                *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
            }
            
            if (SQLITE_ERROR == rc) {
                if (_logsErrors) {
                    NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db));
                    NSLog(@"DB Query: %@", sql);
                }
            }
            else if (SQLITE_MISUSE == rc) {
                // uh oh.
                if (_logsErrors) {
                    NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db));
                    NSLog(@"DB Query: %@", sql);
                }
            }
            else {
                // wtf?
                if (_logsErrors) {
                    NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db));
                    NSLog(@"DB Query: %@", sql);
                }
            }
        }
        
        if (_shouldCacheStatements && !cachedStmt) {
            cachedStmt = [[FMStatement alloc] init];
            
            [cachedStmt setStatement:pStmt];
            
            [self setCachedStatement:cachedStmt forQuery:sql];
            
            FMDBRelease(cachedStmt);
        }
        
        int closeErrorCode;
        
        if (cachedStmt) {
            [cachedStmt setUseCount:[cachedStmt useCount] + 1];
            closeErrorCode = sqlite3_reset(pStmt);
        }
        else {
            /* Finalize the virtual machine. This releases all memory and other
             ** resources allocated by the sqlite3_prepare() call above.
             */
            closeErrorCode = sqlite3_finalize(pStmt);
        }
        
        if (closeErrorCode != SQLITE_OK) {
            if (_logsErrors) {
                NSLog(@"Unknown error finalizing or resetting statement (%d: %s)", closeErrorCode, sqlite3_errmsg(_db));
                NSLog(@"DB Query: %@", sql);
            }
        }
        
        _isExecutingStatement = NO;
        return (rc == SQLITE_DONE || rc == SQLITE_OK);
    }

    我们看完了发现它和executeQuery函数有很多相似的地方,源码标注大家可以看一下executeQuery的标注,也是有几个参数,参数的形式也差不多,就是多了一个error,错误的输出语句。

    2.1.4 执行多条sql

    一次性来执行多条的sql语句对于数据库来说也是常用的操作。FMDB通过使用executeStatements函数来执行多条sql语句

    - (BOOL)executeStatements:(NSString *)sql;系列函数来操作,下面看一下函数实现方式

    - (BOOL)executeStatements:(NSString *)sql {
        return [self executeStatements:sql withResultBlock:nil];
    }

    上面通过调用executeStatements函数调用,我们再进一步看executeStatements的实现方式。

    - (BOOL)executeStatements:(NSString *)sql withResultBlock:(__attribute__((noescape)) 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);
    }

    上面函数,发现有SQLite,其实对sqlite3_exec函数的封装,完成对多条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(@"查询失败");
        }
        
    }

    2.1.6 加解密

    在FMDatabase还有一个功能,就是对FMDB进行加解密处理,下面为实现方式

    #pragma mark Key routines
    
    - (BOOL)rekey:(NSString*)key {
        NSData *keyData = [NSData dataWithBytes:(void *)[key UTF8String] length:(NSUInteger)strlen([key UTF8String])];
        
        return [self rekeyWithData:keyData];
    }
    
    - (BOOL)rekeyWithData:(NSData *)keyData {
    #ifdef SQLITE_HAS_CODEC
        if (!keyData) {
            return NO;
        }
        
        int rc = sqlite3_rekey(_db, [keyData bytes], (int)[keyData length]);
        
        if (rc != SQLITE_OK) {
            NSLog(@"error on rekey: %d", rc);
            NSLog(@"%@", [self lastErrorMessage]);
        }
        
        return (rc == SQLITE_OK);
    #else
    #pragma unused(keyData)
        return NO;
    #endif
    }
    
    - (BOOL)setKey:(NSString*)key {
        NSData *keyData = [NSData dataWithBytes:[key UTF8String] length:(NSUInteger)strlen([key UTF8String])];
        
        return [self setKeyWithData:keyData];
    }
    
    - (BOOL)setKeyWithData:(NSData *)keyData {
    #ifdef SQLITE_HAS_CODEC
        if (!keyData) {
            return NO;
        }
        
        int rc = sqlite3_key(_db, [keyData bytes], (int)[keyData length]);
        
        return (rc == SQLITE_OK);
    #else
    #pragma unused(keyData)
        return NO;
    #endif
    }

    FMDB使用setKey:和 setKeyWithData:输入密码和鉴别身份,通过rekey:和rekeyWithData:来清除和密码和设置密码,在上面的源码大家可以发现,也是对sqlite3_key以及sqlite3_rekey函数的封装。

    上面就是FMDB中FMDatabase类的主要核心代码,希望大家对FMDatabase认识有个提高。

    2.2 FMResultSet

    2.2.1 初始化对象

    + (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB;

    通过上面方法可以看出里面有两个参数

    (1)第一个参数:(FMStatement *)statement

    该对象是对sqlite3_stmt的进一步封装,在sqlite3_stmt *所表示的内容已经不是我们经常使用过的sql语句啦,而是预处理过的语句。

    (2)第二个参数:(FMDatabase*)aDB

    代表结果集所拥有的FMDatabase操作对象。

    下面看一下初始化对象的实现代码

    + (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB {
        
        FMResultSet *rs = [[FMResultSet alloc] init];
        
        [rs setStatement:statement];
        [rs setParentDB:aDB];
        
        NSParameterAssert(![statement inUse]);
        [statement setInUse:YES]; // weak reference
        
        return FMDBReturnAutoreleased(rs);
    }

    2.2.2 遍历结果集合

    - (BOOL)next;

     FMDB通过- (BOOL)next函数完成遍历取结果集合。一起看一下- (BOOL)next;的实现代码

    - (BOOL)next {
        return [self nextWithError:nil];
    }

    上面代码可以发现:- (BOOL)next函数是对SQLite中-(BOOL)nextWithError:(NSError **)outErr函数的封装,主要完成对对象的逐行取值的任务。在深入看下nextWithError函数实现。

    - (BOOL)nextWithError:(NSError **)outErr {
        
        int rc = sqlite3_step([_statement statement]);
        
        if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
            NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]);
            NSLog(@"Database busy");
            if (outErr) {
                *outErr = [_parentDB lastError];
            }
        }
        else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
            // all is well, let's return.
        }
        else if (SQLITE_ERROR == rc) {
            NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
            if (outErr) {
                *outErr = [_parentDB lastError];
            }
        }
        else if (SQLITE_MISUSE == rc) {
            // uh oh.
            NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
            if (outErr) {
                if (_parentDB) {
                    *outErr = [_parentDB lastError];
                }
                else {
                    // If 'next' or 'nextWithError' is called after the result set is closed,
                    // we need to return the appropriate error.
                    NSDictionary* errorMessage = [NSDictionary dictionaryWithObject:@"parentDB does not exist" forKey:NSLocalizedDescriptionKey];
                    *outErr = [NSError errorWithDomain:@"FMDatabase" code:SQLITE_MISUSE userInfo:errorMessage];
                }
                
            }
        }
        else {
            // wtf?
            NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
            if (outErr) {
                *outErr = [_parentDB lastError];
            }
        }
        
        
        if (rc != SQLITE_ROW) {
            [self close];
        }
        
        return (rc == SQLITE_ROW);
    }

    2.2.3 获取行列的值

    通过查看源码发现有以下几处:

    (1)- (int)intForColumnIndex:(int)columnIdx;

    (2)- (long)longForColumnIndex:(int)columnIdx;

    (3)- (long long int)longLongIntForColumnIndex:(int)columnIdx;

    上面三个是根据列的索引获取该列的值。再看三个函数的实现代码

    (1)- (int)intForColumnIndex:(int)columnIdx;

    - (int)intForColumnIndex:(int)columnIdx {
        return sqlite3_column_int([_statement statement], columnIdx);
    }

    (2)- (long)longForColumnIndex:(int)columnIdx

    - (long)longForColumnIndex:(int)columnIdx {
        return (long)sqlite3_column_int64([_statement statement], columnIdx);
    }

    (3)- (long long int)longLongIntForColumnIndex:(int)columnIdx

    - (long long int)longLongIntForColumnIndex:(int)columnIdx {
        return sqlite3_column_int64([_statement statement], columnIdx);
    }

    通过上面三个函数,可以发现上面三个函数实际上是对sqlite3_column_ *函数的封装。

    (4)- (int)intForColumn:(NSString*)columnName;

    (5)- (long)longForColumn:(NSString*)columnName;

    (6)- (long long int)longLongIntForColumn:(NSString*)columnName;

    上面三个方法是根据列的名称取该列的值。下面看一下三个函数具体实现,只举一个即可,其他都是一样实现方式。

    - (int)intForColumn:(NSString*)columnName {
        return [self intForColumnIndex:[self columnIndexForName:columnName]];
    }

    再深入看一下intForColumnIndex实现方式,回到上面啦,(方法(1)看上)。

    - (int)intForColumnIndex:(int)columnIdx {
        return sqlite3_column_int([_statement statement], columnIdx);
    }

    2.2.4 获取行中所有元素

    - (NSDictionary*)resultDictionary:返回值类型为字典。下面是实现方式:

    - (NSDictionary*)resultDictionary {
        
        NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]);
        
        if (num_cols > 0) {
            NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
            
            int columnCount = sqlite3_column_count([_statement statement]);
            
            int columnIdx = 0;
            for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
                
                NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)];
                id objectValue = [self objectForColumnIndex:columnIdx];
                [dict setObject:objectValue forKey:columnName];
            }
            
            return dict;
        }
        else {
            NSLog(@"Warning: There seem to be no columns in this set.");
        }
        
        return nil;
    }

    2.2.5 KVC讲解

    - (void)kvcMagic:(id)object:FMDB中只能对string类型进行支持

    下面是kvcMagic:(id)object实现方式

    - (void)kvcMagic:(id)object {
       // 使用了KVC,将数据库中的每一行数据对应到每一个对象中,对象的属性要和数据库的列名保持一直。
        int columnCount = sqlite3_column_count([_statement statement]);
        
        int columnIdx = 0;
        for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
            
            const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx);
            
            // check for a null row
            if (c) {
                NSString *s = [NSString stringWithUTF8String:c];
                
                [object setValue:s forKey:[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]];
            }
        }
    }

    2.3 FMDatabaseQueue

    FMDB中比较突出优点就是对多线程的处理,而FMDB中对多线程的支持多亏FMDatabaseQueue类。

    2.3.1 初始化队列

    + (instancetype)databaseQueueWithPath:(NSString*)aPath

    实现代码如下:

    + (instancetype)databaseQueueWithPath:(NSString *)aPath {
        FMDatabaseQueue *q = [[self alloc] initWithPath:aPath];
        
        FMDBAutorelease(q);
        
        return q;
    }

    上面函数调用了- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName函数实现如下:

    - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName {
        self = [super init];
        
        if (self != nil) {
            
            _db = [[[self class] databaseClass] databaseWithPath:aPath];
            FMDBRetain(_db);
            
    #if SQLITE_VERSION_NUMBER >= 3005000
            BOOL success = [_db openWithFlags:openFlags vfs:vfsName];
    #else
            BOOL success = [_db open];
    #endif
            if (!success) {
                NSLog(@"Could not create database queue for path %@", aPath);
                FMDBRelease(self);
                return 0x00;
            }
            
            _path = FMDBReturnRetained(aPath);
            //生成一个串行队列
            _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
            //给串行队列生成一个标识
            dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
            _openFlags = openFlags;
            _vfsName = [vfsName copy];
        }
        
        return self;
    }    

    2.3.2 串行执行数据库的操作

    下面看一段代码

    - (void)inDatabase:(void (^)(FMDatabase *db))block {
        /* 使用dispatch_get_specific目的来查看当前queue是否是之前我们设定的那个_queue,如是的话,那么使用kDispatchQueueSpecificKey作为参数就这样传给dispatch_get_specific的话,返回的值是不为空,而且返回值应该就是上面initWithPath:函数中绑定的那个FMDatabaseQueue对象。有人说除了当前queue还有可能有其他什么queue?这就是FMDatabaseQueue的用途,你可以创建多个FMDatabaseQueue对象来并发执行不同的SQL语句。
         另外为什么要判断是不是当前执行的这个queue呢?是为了防止死锁,防止多线程操作出现死锁*/
        FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
        assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
    
        FMDBRetain(self);
    
        dispatch_sync(_queue, ^() {//串行执行block
    
            FMDatabase *db = [self database];
            block(db);
    
            if ([db hasOpenResultSets]) {//调试代码
                NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
    
    #if defined(DEBUG) && DEBUG
                NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
                for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
                    FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
                    NSLog(@"query: '%@'", [rs query]);
                }
    #endif
            }
        });
    
        FMDBRelease(self);
    }

    通过上面代码发现,对于一个queue就是一个串行队列,即使你开启多线程,依然是串行执行的。

    如果大家对队列使用不是很熟悉,可以看一下以前博客可以帮助大家对这方面理解 https://www.cnblogs.com/guohai-stronger/p/9038567.html

    为了方便大家理解,下面举一个例子:

    /**
     *  FMDatabaseQueue实现多线程的案例
     */
    - (void)FMDatabaseQueueMutilThreadTest{
        //1、获取数据库文件的路径
        NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"];
    
        //使用queue1
        FMDatabaseQueue *queue1 = [FMDatabaseQueue databaseQueueWithPath:fileName];
    
        [queue1 inDatabase:^(FMDatabase *db) {
            for (int i=0; i<10; i++) {
                NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]);
            }
        }];
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [queue1 inDatabase:^(FMDatabase *db) {
                for (int i=11; i<20; i++) {
                    NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]);
                }
            }];
        });
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [queue1 inDatabase:^(FMDatabase *db) {
                for (int i=20; i<30; i++) {
                    NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]);
                }
            }];
        });
    }

    通过打印出来结果,看下:

    通过上面发现,虽然开启了多个线程,依然是串行处理数据。

    实际上:FMDatabaseQueue它通过内部创建了一个叫Serial的dispatch_queue_t来处理inDatabase和inTransaction传入的Blocks代码块。FMDatabaseQueue的目的是让我们避免发生并发访问数据库所造成的问题,因为我们对数据库的访问和操作是随机的,如果在里面内置一个Serial队列之后,FMDatabaseQueue这样就变成线程安全的了,达到了线程安全的目的。

    对于同一个queue内部是串行执行,而对于不同的queue,它们是并发执行的。

    2.3.3 关于事物

    对于事物,在数据库中,也是保护数据库的安全一种方式。对于一条sql语句,要么全success,要么全fail。下面是实现代码:

    - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
        [self beginTransaction:NO withBlock:block];
    }
    - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
        FMDBRetain(self);
        dispatch_sync(_queue, ^() { //串行执行,保证线程安全。
    
            BOOL shouldRollback = NO;
    
            if (useDeferred) {
                [[self database] beginDeferredTransaction];// 使用延时性事务
            }
            else {
                [[self database] beginTransaction];// 默认使用独占性事务
            }
    
            block([self database], &shouldRollback);//执行block
    
            if (shouldRollback) {  //根据shouldRollback判断 判断是否回滚,还是提交。
                [[self database] rollback];
            }
            else {
                [[self database] commit];
            }
        });
    
        FMDBRelease(self);
    }

    2.4 FMDatabaseAdditions

    2.4.1 validateSQL

    -(BOOL)validateSQL:(NSString*)sql error:(NSError**)error;通过此方法。校验sql语句是否合法。下面是实现代码

    - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error {
        sqlite3_stmt *pStmt = NULL;
        BOOL validationSucceeded = YES;
        
        int rc = sqlite3_prepare_v2([self sqliteHandle], [sql UTF8String], -1, &pStmt, 0);
        if (rc != SQLITE_OK) {
            validationSucceeded = NO;
            if (error) {
                *error = [NSError errorWithDomain:NSCocoaErrorDomain
                                             code:[self lastErrorCode]
                                         userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage]
                                                                              forKey:NSLocalizedDescriptionKey]];
            }
        }
        
        sqlite3_finalize(pStmt);
        
        return validationSucceeded;
    }

    2.4.2 其他

    columnExists:判断column是否存在;

    tableExists:判断表是否存在。

    2.5 FMDatabasePool

    对于FMDatabasePool,苹果本身并不建议使用,所以我们在这没有必要进行讲解,大家也可以不必追究其内容的实现。

    上面就是FMDB源码解析的全部内容,大家主要对2.1,2.2,2.3内容进行详细看就可以啦,2.4和2.5了解即可。希望上面的FMDB源码讲解对大家对FMDB认识有所加强,欢迎大家指正。

    我们已经讲完了SQLite和FMDB源码解析,下一篇我们将讲述SQLite和FMDB在使用上的区别。

  • 相关阅读:
    邻接表(spfa模版)
    翻咸鱼(???)
    求逆序数
    Symmetry CSU
    Highways
    LightOJ
    G
    最长的斜坡。。。。
    快速幂取模
    二分
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/9246653.html
Copyright © 2011-2022 走看看