FMDB 源碼解析 FMResultSet類

背景

基於目前項目中老是遇到fmdb的crash,網上找一圈以後也沒有找到思路,因此有了讀源碼的衝動,但願在過程當中能分析出問題的所在,下降crash率。php

本文以類做爲突破口。過程當中可能會對系統sqlite3的部分地方進行簡單分析。html


文件組成

FMDB源碼主要有一下幾個文件組成:sql

FMDatabase:表示一個單獨的SQLite DB實例,經過它能夠對數據庫進行增刪改查等操做。數據庫

FMResultSet:表示經過sql在DB中查詢到的結果集,而且將查詢結果轉化成對應的值或對象,例如:int、long、bool、NSString、NSDate、NSData、char *、 id等。bash

FMDatabaseQueue:用來管理數據查詢的隊列,保證大部分時間下對數據庫的操做是串行的。app

FMDatabaseAdditions:做爲 FMDatabase類的拓展。新增了一些經常使用的校驗方法,例如:表是否存在、列是否存在、版本號、sql校驗等。ui

FMDatabasePool: 用來管理數據庫查詢任務。不過在頭文件中,做者寫的很是清楚牆裂不建議使用,而是用 FMDatabaseQueue代替。若是必定要用的話,必定要注意死鎖。this

FMResultSet類

初始化

+ (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);
}
複製代碼

參數1:FMStatementspa

  1. 這個對象是對 sqlite3_stmt的封裝,在使用的時候通常你們不會直接使用 FMStatement,只須要 FMDatabaseFMStatement 交互;
  2. 對象中除了封裝了 sqlite3_stmt 還拓展了 queryuseCountinUse 字段,並實現了 statementclosereset 方法。
拓展:sqlite3_stmt--> http://www.sqlite.org/c3ref/stmt.html
* sqlite3_stmt 表示已編爲二進制形式且能夠進行執行的單個SQL語句。
* 把每一個SQL語句看做一個單獨的計算機程序。原始的SQL文本是源代碼。準備好的statement是已編譯的對象代碼。必須先將全部SQL轉換爲準備好的statement,而後才能運行它。

* 生命週期
1.sqlite3_prepare_v2() 建立對象
2.sqlite3_bind_*() Bind values to parameters 
3.sqlite3_step()  執行sql
4.sqlite3_reset() 重置statement,而後執行第2步
5.sqlite3_finalize() 銷燬對象

複製代碼

參數2:FMDatabasecode

  1. result 查詢的數據庫

遍歷的結果集合

- (BOOL)next {
    
    int rc = sqlite3_step([_statement statement]);  //執行sql,返回狀態
    
    if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
        NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]);
        NSLog(@"Database busy");
    }
    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])); } else if (SQLITE_MISUSE == rc) { // uh oh. NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); } else { // wtf? NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); } if (rc != SQLITE_ROW) { [self close]; } return (rc == SQLITE_ROW);//sqlite3_step() has another row ready } 複製代碼
  1. int rc = sqlite3_step([_statement statement]); 執行sql,返回狀態
  2. 判斷一:SQLITE_BUSYSQLITE_LOCKED 數據庫被鎖或者數據庫忙碌
  3. 判斷二:SQLITE_DONESQLITE_ROW 執行完畢,沒有發生錯誤
  4. 判斷三:SQLITE_ERROR 通用錯誤
  5. 判斷四: SQLITE_MISUSE -->Library used incorrectly 庫錯
  6. 判斷五:SQLITE_ROW 若是下一行沒有reday,則直接關閉,因此在使用while([rs next])的時候不須要手動關閉

關閉rs

- (void)close {
    [_statement reset];
    FMDBRelease(_statement);
    _statement = nil;
    
    // we don't need this anymore... (i think) //[_parentDB setInUse:NO]; [_parentDB resultSetDidClose:self]; [self setParentDB:nil]; } 複製代碼

重置statement,將statement對象置nil,釋放當前db對象

列名與索引對應關係

- (NSMutableDictionary *)columnNameToIndexMap {
  if (!_columnNameToIndexMap) {
      int columnCount = sqlite3_column_count([_statement statement]);
      _columnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)columnCount];
      int columnIdx = 0;
      for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
          [_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx]
                                    forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]];
      }
  }
  return _columnNameToIndexMap;
}
複製代碼
  • 經過可執行的二進制 statement 獲取到返回的列總數N,並建立一個默認N個對象的字典
  • 將列名的 lowercaseString 做爲key值,綁定列的index。
- (void)kvcMagic:(id)object {
    
    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)]];
        }
    }
}
複製代碼
  • 使用KVC,把數據庫中的每一行數據映射到每個對象,對象的屬性要和數據庫的列名保持一致。

獲取列裏面的值

  • -XXXForColumnIndex:(int)columnIdx;根據列的索引獲取該列的值。

  • -XXXForColumn:(NSString*)columnName;根據列的名稱獲取該列的值。

- (NSString*)stringForColumnIndex:(int)columnIdx {
    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
        return nil;
    }
    const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx);
    if (!c) {
        // null row.
        return nil;
    }
    return [NSString stringWithUTF8String:c];
}
複製代碼
  • 先判異常,而後取值
拓展:——> http://www.sqlite.org/c3ref/column_blob.html
sqlite3_column_blob	→	BLOB result
sqlite3_column_double	→	REAL result
sqlite3_column_int	→	32-bit INTEGER result
sqlite3_column_int64	→	64-bit INTEGER result
sqlite3_column_text	→	UTF-8 TEXT result
sqlite3_column_text16	→	UTF-16 TEXT result
sqlite3_column_value	→	The result as an unprotected sqlite3_value object.
sqlite3_column_bytes	→	Size of a BLOB or a UTF-8 TEXT result in bytes
sqlite3_column_bytes16  	→  	Size of UTF-16 TEXT in bytes
sqlite3_column_type	→	Default datatype of the result
複製代碼
根據列名獲取該列的值
- (NSString*)stringForColumn:(NSString*)columnName {
    return [self stringForColumnIndex:[self columnIndexForName:columnName]];
}
複製代碼

將結果中列和值映射成字典

- (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;
}
複製代碼
  • sqlite3_data_count(P) returns the number of columns in the current row of the result set of [prepared statement] P
  • 經過遍歷列數,一一賦值

寫在最後

1.因爲替換最新版fmdb後crash增多,爲保證工程穩定性恢復至歷史版本,所以分析的大部分源碼會貼至文章。

2.歡迎你們對文章給出建議或意見。

3.本文凝結了做者的心血,但願你們在轉發、傳閱的時候可以保留文章的初始地址。

參考連接:

1.www.code4app.com/home.php?mo…

2.www.sqlite.org/index.html

相關文章
相關標籤/搜索