iOS开发源码阅读篇--FMDB源码分析1(FMResultSet)

一、前言

FMDB是IOS平台的SQLite数据库框架,以OC的方式封装了SQLite的C语言的API。FMDB使用起来更加的面向对象,省去了很多麻烦、冗余的C语言代码具体对比详见我的简书iOS开发数据存储篇—libsqlite3和FMDB的基本使用和区别,对比苹果自带的Core Data框架,更加的轻量级和灵活。提供了多线程安全的数据库操作的方法,有效的防止数据混乱。开源地址为https://github.com/ccgus/fmdb

这是一个我的iOS交流群:937194184,群文件自行下载,不管你是小白还是大牛热烈欢迎进群 ,分享面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。——点击:加入

二、源码分析

FMDB源码主要有以下几个文件组成:

FMResultSet : 表示FMDatabase执行查询之后的结果集。

FMDatabase : 表示一个单独的SQLite数据库操作实例,通过它可以对数据库进行增删改查等等操作。

FMDatabaseAdditions : 扩展FMDatabase类,新增对查询结果只返回单个值的方法进行简化,对表、列是否存在,版本号,校验SQL等等功能。

FMDatabaseQueue : 使用串行队列 ,对多线程的操作进行了支持。

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交流群:937194184,群文件自行下载,不管你是小白还是大牛热烈欢迎进群 ,分享面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。——点击:加入

如果觉得对你还有些用,就关注小编+喜欢这一篇文章。你的支持是我继续的动力。

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

文章来源于网络,如有侵权,请联系小编删除。

    原文作者:扒皮狼
    原文地址: https://www.jianshu.com/p/79f600aa9602
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞