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

1、前言

FMDB是IOS平臺的SQLite數據庫框架,以OC的方式封裝了SQLite的C語言的API。FMDB使用起來更加的面向對象,省去了不少麻煩、冗餘的C語言代碼具體對比詳見個人簡書iOS開發數據存儲篇—libsqlite3和FMDB的基本使用和區別,對比蘋果自帶的Core Data框架,更加的輕量級和靈活。提供了多線程安全的數據庫操做的方法,有效的防止數據混亂。開源地址爲github.com/ccgus/fmdbgit

這是一個個人iOS交流羣:624212887,羣文件自行下載,無論你是小白仍是大牛熱烈歡迎進羣 ,分享面試經驗,討論技術, 你們一塊兒交流學習成長!但願幫助開發者少走彎路。——點擊:加入github

2、源碼分析

FMDB源碼主要有如下幾個文件組成:面試

FMResultSet : 表示FMDatabase執行查詢以後的結果集。sql

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

FMDatabaseAdditions : 擴展FMDatabase類,新增對查詢結果只返回單個值的方法進行簡化,對錶、列是否存在,版本號,校驗SQL等等功能。安全

FMDatabaseQueue : 使用串行隊列 ,對多線程的操做進行了支持。bash

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

FMDB這裏的支持仍是比較簡單的,只能對於String類型的屬性進行支持。

/**
 *  使用KVC,把數據庫中的每一行數據對應到每個對象,對象的屬性要和數據庫的列名保持一直。
 *
 *  @param object 對象
 */
- (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)]];
        }
    }
}
複製代碼

3、最後說一點

這是一個個人iOS交流羣:624212887,羣文件自行下載,無論你是小白仍是大牛熱烈歡迎進羣 ,分享面試經驗,討論技術, 你們一塊兒交流學習成長!但願幫助開發者少走彎路。——點擊:加入

若是以爲對你還有些用,就關注小編+喜歡這一篇文章。你的支持是我繼續的動力。

下篇文章預告:·FMDB源碼分析2(FMDatabase+FMDatabaseAdditions)

文章來源於網絡,若有侵權,請聯繫小編刪除。

相關文章
相關標籤/搜索