FMDB 源码解析 FMResultSet类

1,525 阅读4分钟

背景

基于目前项目中总是遇到fmdb的crash,网上找一圈之后也没有找到思路,所以有了读源码的冲动,希望在过程中能分析出问题的所在,降低crash率。

本文以类作为突破口。过程中可能会对系统sqlite3的部分地方进行简单分析。


文件组成

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

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

FMResultSet:表示通过sql在DB中查询到的结果集,并且将查询结果转化成对应的值或对象,例如:int、long、bool、NSString、NSDate、NSData、char *、 id等。

FMDatabaseQueue:用来管理数据查询的队列,保证大部分时间下对数据库的操作是串行的。

FMDatabaseAdditions:作为 FMDatabase类的拓展。新增了一些常用的校验方法,例如:表是否存在、列是否存在、版本号、sql校验等。

FMDatabasePool: 用来管理数据库查询任务。不过在头文件中,作者写的非常清楚墙裂不建议使用,而是用 FMDatabaseQueue代替。如果一定要用的话,一定要注意死锁。

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:FMStatement

  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:FMDatabase

  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