1. 程式人生 > >IOS開發原始碼閱讀篇--FMDB原始碼分析1(FMResultSet)

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 = 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)]];
        }
    }
}

三、聯絡方式

微博:新浪微博

相關推薦

IOS開發原始碼閱讀--FMDB原始碼分析1(FMResultSet)

一、前言 FMDB是IOS平臺的SQLite資料庫框架,以OC的方式封裝了SQLite的C語言的API。FMDB使用起來更加的面向物件,省去了很多麻煩、冗餘的C語言程式碼(具體對比詳見我的部落格IOS開發資料儲存篇—libsqlite3和FMDB的基本使用和區

【Spring原始碼閱讀】 preInstantiateSingletons方法分析,單例Bean獲取/例項化流程

在初始化ClassPathXmlApplicatonContext過程中,核心初始化邏輯在AbstractApplicationContext的refresh函式中: public void refresh() throws BeansException, IllegalStateE

kubernetes原始碼閱讀之整體架構分析

Kubernetes是Google開源的Docker容器叢集管理系統,為容器化的應用提供資源排程、部署執行、服務發現、擴容縮容等整一套功能。 整個k8s架構圖如下所示 整個k8s架構包括兩個元件:master(APIs、scheduler、replication con

Spring原始碼閱讀-ApplicationContext體系結構分析

目錄 繼承層次圖概覽 ConfigurableApplicationContext分析 AbstractApplicationContext GenericApplicationContext

iOS開發之Appstore——版本更新

ise block store win nsstring oot apps url root 1.版本更新方法 + (void)updateWithAPPID:(NSString *)appid back:(void (^)(NSString *, NSString

Zookeeper原始碼閱讀(九) ZK Client-Server(1)

前言 Watcher部分的程式碼量總的來說還是比較多的,但是整個邏輯流程還是相對來說比較清晰的。不過還是需要常在腦子裡過一過,zk的watcher的相關的架構的設計還是挺精妙的。 從這一篇起開始說ZK client端-server端互動相關的程式碼,主要是從client本身,client和server的連

【Spring原始碼閱讀】Spring原始碼閱讀環境搭建

閱讀Spring原始碼,我們可以通過以下兩種方式: 直接在Idea/Eclipse上建立自己的java maven/gradle專案,匯入Spring特定版本依賴Jar和原始碼,直接執行測試程式碼,逐步除錯閱讀 從github直接獲取Spring原始碼專案,基於Spri

iOS開發之Crash日誌獲取與分析

當在非除錯狀態下,我們用真機測試app,crash或者說閃退是一件很常見的事,最讓我們開發人員頭疼的是,自己在開發過程中總是不會遇到crash,安裝到別人的裝置,就出現了閃退崩潰現象。這種偶現的、概率比較低的閃退是最令人頭疼。 這時iOS crash log 

iOS 開發 -- Swift 語法 (二) 可選項

Optional 是 Swift 的一大特色,也是 Swift 初學者最容易困惑的問題定義變數時,如果指定是可選的,表示該變數可以有一個指定型別的值,也可以是 nil定義變數時,在型別後面新增一個 ?,表示該變數是可選的變數可選項的預設值是 nil常量可選項沒有預設值,主要

iOS開發之基礎(14)—— Block

版本 Xcode 9.1 block簡介 block是一個OC物件,於iOS4開始引入。其本身封裝了一段程式碼,可被當作變數、當作引數或作為返回值。block常用於GCD、動畫、排序及各類回撥傳值中。 block程式碼結構圖 注:圖片來自

ArcGIS Runtime SDK for IOS 開發之啟程

   從事GIS學習有幾年的時間,而將IOS和GIS結合起來進行學習和研究正是筆者工作這半年多來所做的事情。其實剛開始就想以部落格的形式來記錄自己學習的心得和體會,但是總是被各種事情和藉口所耽誤,對於這種行為樓主也只能是無奈的呵呵了,不過本人有個特點,就是永遠堅信這麼一個原

iOS開發網絡—網絡編程基礎

cal 前端開發 eclipse 如果 版本 京東商城 img jdk 由器 一、為什麽要學習網絡編程 1.簡單說明 在移動互聯網時代,移動應用的特征有: (1)幾乎所有應用都需要用到網絡,比如QQ、微博、網易新聞、優酷、百度地圖 (2)只有通過網絡跟外界進行數據交互、數據

iOS開發】記錄一次蘋果2.1大禮包被拒絕3次歷時12天的坑逼上架歷程

##一、場景描述(歷時12天的上架歷程) ##二、解決方案 ###2.1 第一次被拒絕原因(網上傳言的“狗年大禮包”) 發件人 Apple Guideline 2.1 - Information Needed This type of app has

Spark修煉之道(高階)——Spark原始碼閱讀:第十二節 Spark SQL 處理流程分析

作者:周志湖 下面的程式碼演示了通過Case Class進行表Schema定義的例子: // sc is an existing SparkContext. val sqlContext = new org.apache.spark.sql.SQLConte

iOS 開發FMDB 源碼分析

lob nts st2 current pan mask 數據庫操作 數據庫 nbsp   概念:   FMDB 是用於數據存儲的框架,它是 iOS 平臺下對 SQLite 數據庫的封裝。FMDB 是面向對象的,它以 OC 的方式封裝了 SQLite 的 C 語言 API,

嵌入式Linux開發——(十一)u-boot原始碼分析

1、U-Boot的特性:     ①開放原始碼     ②支援多種嵌入式作業系統核心:Linux、NetBSD、VxWorks、QNx、RTEMS、ARTOS、 LynxOS     ③支援多種架構的CPU:Power

【.NET Core專案實戰-統一認證平臺】第八章 授權-IdentityServer4原始碼分析

原文: 【.NET Core專案實戰-統一認證平臺】第八章 授權篇-IdentityServer4原始碼分析 【.NET Core專案實戰-統一認證平臺】開篇及目錄索引 上篇文章我介紹瞭如何在閘道器上實現客戶端自定義限流功能,基本完成了關於閘道器的一些自定義擴充套件需求,後面幾篇將介紹基於Ident

原始碼中Android短視訊開發iOS短視訊開發中記錄位置的原始碼

在原始碼中Android短視訊開發和iOS短視訊開發中,記錄位置資訊的功能是很重要的,它主要被用來定位使用者、搜尋附近的人、查詢使用者可能認識的人,並向用戶推薦他們。下面這段程式碼資訊就是短視訊開發過程中位置資訊處理簡介。 /*記錄位置資訊*/ private void sendLocati

netty原始碼閱讀之解碼值基於固定長度解碼器分析

固定長度解碼器FixedLengthFrameDecoder比較簡單,我們看下它類的註釋: /** * A decoder that splits the received {@link ByteBuf}s by the fixed number * of bytes.

netty原始碼閱讀之解碼之基於長度域解碼器引數分析

這篇文章我們放鬆一點,只分析基於長度域解碼器的幾個引數, lengthFieldOffset :長度域的偏移量,也就是長度域要從什麼地方開始 lengthFieldLength:長度域的長度,也就是長度域佔多少個位元組 lengthAdjustment:長度域的值的調整