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在使用上的區別。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM