天天看點

IOS開發源碼閱讀篇--FMDB源碼分析1(FMResultSet)

一、前言

FMDB是IOS平台的SQLite資料庫架構,以OC的方式封裝了SQLite的C語言的API。FMDB使用起來更加的面向對象,省去了很多麻煩、備援的C語言代碼(具體對比詳見我的部落格IOS開發資料存儲篇—libsqlite3和FMDB的基本使用和差別),對比蘋果自帶的Core Data架構,更加的輕量級和靈活。提供了多線程安全的資料庫操作的方法,有效的防止資料混亂。開源位址為https://github.com/ccgus/fmdb。

二、源碼分析

FMDB源碼主要有以下幾個檔案組成:

  1. FMResultSet : 表示FMDatabase執行查詢之後的結果集。
  2. FMDatabase : 表示一個單獨的SQLite資料庫操作執行個體,通過它可以對資料庫進行增删改查等等操作。
  3. FMDatabaseAdditions : 擴充FMDatabase類,新增對查詢結果隻傳回單個值的方法進行簡化,對表、列是否存在,版本号,校驗SQL等等功能。
  4. FMDatabaseQueue : 使用串行隊列 ,對多線程的操作進行了支援。
  5. FMDatabasePool : 使用任務池的形式,對多線程的操作提供支援。(不過官方對這種方式并不推薦使用,優先選擇FMDatabaseQueue的方式:ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD)

下面我們就來逐個分析FMDB源碼的實作方式,先講FMResultSet的實作思路。

2.1:FMResultSet

2.1.1:初始化對象

  • 參數1:(FMStatement *)statement

    該對象主要是對sqlite3_stmt的封裝,sqlite3_stmt * 所表示的内容可以看成是預處理過得sql語句,已經不是我們熟知的sql語句。他是一個已經把sql語句解析了,用sqlite自己表示記錄的内部資料結構。

  • 參數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]; 

    return FMDBReturnAutoreleased(rs);
}
           

2.1.2:周遊取得所有的結果集合

-(BOOL)next;其實是對-(BOOL)nextWithError:(NSError **)outErr;函數的封裝。主要作用是通過sqlite3_step函數對FMStatement中的sqlite3_stmt對象進行逐行取值。

/**
 *  周遊每一行的資料(fmdb:next() --》c:sqlite3_step() )
 *
 *  @param outErr 錯誤資訊
 *
 *  @return
 */
- (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.1.3:列名與該列的列數的一一對應關系

  • @property (readonly) NSMutableDictionary *columnNameToIndexMap;對象中維護了列名與索引一一對應的關系的對照表。
    /**
    *  列的名稱與索引的一一對應關系
    *
    *  @return 
    *  @{ @“id”:@0,
    *     @"name":@1,
    *      @"age":@2
    *    }
    */
    - (NSMutableDictionary *)columnNameToIndexMap {
      if (!_columnNameToIndexMap) {
          int columnCount = sqlite3_column_count([_statement statement]);
          _columnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)columnCount];
          int columnIdx = ;
          for (columnIdx = ; columnIdx < columnCount; columnIdx++) {
              [_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx]
                                        forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]];
          }
      }
      return _columnNameToIndexMap;
    }
               
  • -(int)columnIndexForName:(NSString*)columnName; 根據列名擷取該列的所在第幾列(列的索引)
  • -(NSString*)columnNameForIndex:(int)columnIdx;根據列的索引擷取該列的名稱。

2.1.4:獲得每一行中每一個列字段的值。

  • -XXXForColumnIndex:(int)columnIdx;根據列的索引擷取該列的值。
  • -XXXForColumn:(NSString*)columnName;根據列的名稱擷取該列的值。

    -XXXForColumnIndex:(int)columnIdx;其實是對sqlite3_column_*函數的封裝。如下所示。

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

由2.1.3中columnNameToIndexMap我們可以得到列名與索引的一一對象關系,那麼-XXXForColumn:(NSString*)columnName;的實作就很簡單了。

/**
 *  根據列的名稱擷取int值
 *
 *  @param columnName
 *
 *  @return
 */
- (int)intForColumn:(NSString*)columnName {
    return [self intForColumnIndex:[self columnIndexForName:columnName]];
}
           

2.1.5:擷取每一行中所有的結果集合

/**
 *  每一行資料的結果所對應的Dictionary
 *
 *  @return 
 *   @{
 *   age = 29;
 *   id = 1;
 *   name = "yixiang-20";
 *   }
 */
- (NSDictionary*)resultDictionary {

    NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]);

    if (num_cols > ) {
        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];

        int columnCount = sqlite3_column_count([_statement statement]);

        int columnIdx = ;
        for (columnIdx = ; 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.1.6:對KVC的支援

FMDB這裡的支援還是比較簡單的,隻能對于String類型的屬性進行支援。

/**
 *  使用KVC,把資料庫中的每一行資料對應到每一個對象,對象的屬性要和資料庫的列名保持一直。
 *
 *  @param object 對象
 */
- (void)kvcMagic:(id)object {

    int columnCount = sqlite3_column_count([_statement statement]);

    int columnIdx = ;
    for (columnIdx = ; 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)]];
        }
    }
}
           

三、聯系方式

微網誌:新浪微網誌

部落格:http://blog.csdn.net/yixiangboy

github:https://github.com/yixiangboy