上一篇博客讲述SQLite的使用,本篇将讲述FMDB源码,后面也会讲解SQLite在使用与FMDB的区别。本篇读下来大约20-30分钟,建议大家先收藏一下。

FMDB是以OC方式封装SQLite中C语言的API,也是iOS中SQLite数据库的框架,在目前研发项目中使用的也是比较广泛的。下面直入正题

一、FMDB源码结构

首先我们来看一下FMDB的源码的结构与组成,如下图:

我们可以从结构上看出FMDB在共有5个文件组成,其中FMDB.h用于管理其他5个文件,下面分别讲述5个文件的用处

(1)FMDatabase:代表一个单独的SQLite操作实例,数据库通过它增删改查操作;

(2)FMResultSet:代表查询后的结果集;

(3)FMDatabaseQueue:代表串行队列,对多线程操作提供了支持;

(4)FMDatabaseAdditions:本类用于扩展FMDatabase,用于查找表是否存在,版本号等功能;

(5)FMDatabasePool:此方式官方是不推荐使用,代表是任务池,也是对多线程提供了支持。

下面将具体讲述每一个类的核心代码是怎么样的?

二、FMDB源码解析

2.1 FMDatabase源码解析

2.1.1 打开数据库连接

- (BOOL)open;是对SQLite中sqlite3_open()函数的封装使用

下面看一下具体使用

- (BOOL)open {
if (_isOpen) {
return YES;
}
if (_db) {
[self close];
}
// now open database
int err = sqlite3_open([self sqlitePath], (sqlite3**)&_db );
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
}
//当执行这段代码的时候,数据库正在被其他线程访问,就需要给他设置重试时间,
if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
} _isOpen = YES; return YES;
} //下面我们看一下setMaxBusyRetryTimeInterval的实现方法
- (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout { _maxBusyRetryTimeInterval = timeout; if (!_db) {
return;
} if (timeout > ) {
sqlite3_busy_handler(_db, &FMDBDatabaseBusyHandler, (__bridge void *)(self));
}
else {
// turn it off otherwise
sqlite3_busy_handler(_db, nil, nil);
}
}

上面是打开数据库连接,上面红色字体标注的方法setMaxBusyRetryTimeInterval()设置重试时间,相当于SQLite中调用int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);

针对int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);该函数

(1)第一个参数:哪个数据库需要设置busy_handler

(2)第二个参数:需要回调的busy handler,调用次函数时,需要传参,是sqlite3_busy_handler第三个参数

(3)第三个参数:int参数代表锁事件,该函数被调用次数,如果返回为0,不会再次访问数据库,返回非0,将不断尝试访问数据库。

当获取不到锁时,会执行回调函数的次数以此来延时,等待其他线程等操作完数据库,这样获得操作数据库。

2.1.2 查询数据库

executeQuery函数是数据库比较重要的方法。

在看实现文件

- (FMResultSet *)executeQuery:(NSString*)sql, ... {
va_list args;
va_start(args, sql);
//整个方法关键是下面一句
id result = [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args]; va_end(args);
return result;
}

从上面可以看出:调用executeQuery函数,实际上是调用

- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args函数,下面看下此函数的实现方式。

- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
//判断数据库是否存在
if (![self databaseExists]) {
return 0x00;
}
//判断数据库是否已经在使用当中
if (_isExecutingStatement) {
[self warnInUse];
return 0x00;
} _isExecutingStatement = YES; int rc = 0x00;
sqlite3_stmt *pStmt = 0x00;
FMStatement *statement = 0x00;
FMResultSet *rs = 0x00;
//打印sql语句
if (_traceExecution && sql) {
NSLog(@"%@ executeQuery: %@", self, sql);
} //获取缓存数据
if (_shouldCacheStatements) {
statement = [self cachedStatementForQuery:sql];
pStmt = statement ? [statement statement] : 0x00;
[statement reset];
} //没有缓存数据,直接查询数据库
if (!pStmt) {
//对sql语句进行预处理,生成预处理过的“sql语句”pStmt。
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -, &pStmt, ); if (SQLITE_OK != rc) {//出错处理
if (_logsErrors) {
NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
NSLog(@"DB Query: %@", sql);
NSLog(@"DB Path: %@", _databasePath);
} if (_crashOnErrors) {
NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
abort();
} sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return nil;
}
} id obj;
int idx = ;
int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!) // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
//对dictionaryArgs参数的处理,类似于下面":age"参数形式
if (dictionaryArgs) { for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { // Prefix the key with a colon.
NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; if (_traceExecution) {
NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
} // Get the index for the parameter name.
int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); FMDBRelease(parameterName); if (namedIdx > ) {
// Standard binding from here.
[self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
// increment the binding count, so our check below works out
idx++;
}
else {
NSLog(@"Could not find index for %@", dictionaryKey);
}
}
}
else {//对于arrayArgs参数和不定参数的处理,类似于"?"参数形式 while (idx < queryCount) { if (arrayArgs && idx < (int)[arrayArgs count]) {
obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
}
else if (args) {//不定参数形式
obj = va_arg(args, id);
}
else {
//We ran out of arguments
break;
} if (_traceExecution) {
if ([obj isKindOfClass:[NSData class]]) {
NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]);
}
else {
NSLog(@"obj: %@", obj);
}
} idx++; [self bindObject:obj toColumn:idx inStatement:pStmt];
}
} if (idx != queryCount) {//如果绑定的参数数目不对,则进行出错处理
NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return nil;
} FMDBRetain(statement); // to balance the release below if (!statement) {//生成FMStatement对象
statement = [[FMStatement alloc] init];
[statement setStatement:pStmt];
//缓存的处理,key为sql语句,值为statement
if (_shouldCacheStatements && sql) {
[self setCachedStatement:statement forQuery:sql];
}
} // the statement gets closed in rs's dealloc or [rs close];
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
[rs setQuery:sql]; NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
[_openResultSets addObject:openResultSet]; [statement setUseCount:[statement useCount] + ]; FMDBRelease(statement); _isExecutingStatement = NO; return rs;
}

发现上面那个函数有四个参数,看到源码之后,我们一一讲述四个参数:

(1)第一个参数sql:代表我们要查询的sql语句;

(2)第二个参数arrayArgs:代表数组类型的参数,举例如下:

FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?" withArgumentsInArray:@[@]];

(3)第三个参数dictionaryArgs:代表字典类型的参数,举例如下:

    FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > :age" withParameterDictionary:@{@"age":@}];

(4)第四个参数args:代表可变类型的参数,举例如下:

FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?",@()];

上面是查询基本源码讲解,大家可以详细里面的源码实现,里面也有讲解。

2.1.3 更新数据库

针对FMDB数据库增删改都属于对数据库的更新操作,FMDB通过executeUpdate系列函数来实现对数据库的更新操作。

- (BOOL)executeUpdate:(NSString*)sql, ...;系列函数,我们来看一下executeUpdate函数的实现。

- (BOOL)executeUpdate:(NSString*)sql, ... {
va_list args;
va_start(args, sql);
//主要是下面这个函数
BOOL result = [self executeUpdate:sql error:nil withArgumentsInArray:nil orDictionary:nil orVAList:args]; va_end(args);
return result;
}

我们再来看一下红色标出的代码实现,- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args函数实现。

#pragma mark Execute updates

- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {

    if (![self databaseExists]) {
return NO;
} if (_isExecutingStatement) {
[self warnInUse];
return NO;
} _isExecutingStatement = YES; int rc = 0x00;
sqlite3_stmt *pStmt = 0x00;
FMStatement *cachedStmt = 0x00; if (_traceExecution && sql) {
NSLog(@"%@ executeUpdate: %@", self, sql);
} if (_shouldCacheStatements) {
cachedStmt = [self cachedStatementForQuery:sql];
pStmt = cachedStmt ? [cachedStmt statement] : 0x00;
[cachedStmt reset];
} if (!pStmt) {
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -, &pStmt, ); if (SQLITE_OK != rc) {
if (_logsErrors) {
NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
NSLog(@"DB Query: %@", sql);
NSLog(@"DB Path: %@", _databasePath);
} if (_crashOnErrors) {
NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
abort();
} if (outErr) {
*outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
} sqlite3_finalize(pStmt); _isExecutingStatement = NO;
return NO;
}
} id obj;
int idx = ;
int queryCount = sqlite3_bind_parameter_count(pStmt); // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
if (dictionaryArgs) { for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { // Prefix the key with a colon.
NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; if (_traceExecution) {
NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
}
// Get the index for the parameter name.
int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); FMDBRelease(parameterName); if (namedIdx > ) {
// Standard binding from here.
[self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]; // increment the binding count, so our check below works out
idx++;
}
else {
NSString *message = [NSString stringWithFormat:@"Could not find index for %@", dictionaryKey]; if (_logsErrors) {
NSLog(@"%@", message);
}
if (outErr) {
*outErr = [self errorWithMessage:message];
}
}
}
}
else { while (idx < queryCount) { if (arrayArgs && idx < (int)[arrayArgs count]) {
obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
}
else if (args) {
obj = va_arg(args, id);
}
else {
//We ran out of arguments
break;
} if (_traceExecution) {
if ([obj isKindOfClass:[NSData class]]) {
NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]);
}
else {
NSLog(@"obj: %@", obj);
}
} idx++; [self bindObject:obj toColumn:idx inStatement:pStmt];
}
} if (idx != queryCount) {
NSString *message = [NSString stringWithFormat:@"Error: the bind count (%d) is not correct for the # of variables in the query (%d) (%@) (executeUpdate)", idx, queryCount, sql];
if (_logsErrors) {
NSLog(@"%@", message);
}
if (outErr) {
*outErr = [self errorWithMessage:message];
} sqlite3_finalize(pStmt);
_isExecutingStatement = NO;
return NO;
} /* Call sqlite3_step() to run the virtual machine. Since the SQL being
** executed is not a SELECT statement, we assume no data will be returned.
*/ rc = sqlite3_step(pStmt); if (SQLITE_DONE == rc) {
// all is well, let's return.
}
else if (SQLITE_INTERRUPT == rc) {
if (_logsErrors) {
NSLog(@"Error calling sqlite3_step. Query was interrupted (%d: %s) SQLITE_INTERRUPT", rc, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
}
else if (rc == SQLITE_ROW) {
NSString *message = [NSString stringWithFormat:@"A executeUpdate is being called with a query string '%@'", sql];
if (_logsErrors) {
NSLog(@"%@", message);
NSLog(@"DB Query: %@", sql);
}
if (outErr) {
*outErr = [self errorWithMessage:message];
}
}
else {
if (outErr) {
*outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
} if (SQLITE_ERROR == rc) {
if (_logsErrors) {
NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
}
else if (SQLITE_MISUSE == rc) {
// uh oh.
if (_logsErrors) {
NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
}
else {
// wtf?
if (_logsErrors) {
NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
}
} if (_shouldCacheStatements && !cachedStmt) {
cachedStmt = [[FMStatement alloc] init]; [cachedStmt setStatement:pStmt]; [self setCachedStatement:cachedStmt forQuery:sql]; FMDBRelease(cachedStmt);
} int closeErrorCode; if (cachedStmt) {
[cachedStmt setUseCount:[cachedStmt useCount] + ];
closeErrorCode = sqlite3_reset(pStmt);
}
else {
/* Finalize the virtual machine. This releases all memory and other
** resources allocated by the sqlite3_prepare() call above.
*/
closeErrorCode = sqlite3_finalize(pStmt);
} if (closeErrorCode != SQLITE_OK) {
if (_logsErrors) {
NSLog(@"Unknown error finalizing or resetting statement (%d: %s)", closeErrorCode, sqlite3_errmsg(_db));
NSLog(@"DB Query: %@", sql);
}
} _isExecutingStatement = NO;
return (rc == SQLITE_DONE || rc == SQLITE_OK);
}

我们看完了发现它和executeQuery函数有很多相似的地方,源码标注大家可以看一下executeQuery的标注,也是有几个参数,参数的形式也差不多,就是多了一个error,错误的输出语句。

2.1.4 执行多条sql

一次性来执行多条的sql语句对于数据库来说也是常用的操作。FMDB通过使用executeStatements函数来执行多条sql语句

- (BOOL)executeStatements:(NSString *)sql;系列函数来操作,下面看一下函数实现方式

- (BOOL)executeStatements:(NSString *)sql {
return [self executeStatements:sql withResultBlock:nil];
}

上面通过调用executeStatements函数调用,我们再进一步看executeStatements的实现方式。

- (BOOL)executeStatements:(NSString *)sql withResultBlock:(__attribute__((noescape)) FMDBExecuteStatementsCallbackBlock)block {

    int rc;
char *errmsg = nil; rc = sqlite3_exec([self sqliteHandle], [sql UTF8String], block ? FMDBExecuteBulkSQLCallback : nil, (__bridge void *)(block), &errmsg); if (errmsg && [self logsErrors]) {
NSLog(@"Error inserting batch: %s", errmsg);
sqlite3_free(errmsg);
} return (rc == SQLITE_OK);
}

上面函数,发现有SQLite,其实对sqlite3_exec函数的封装,完成对多条sql语句的查找。这样讲可能不是很清晰,举例一下:

   //多个SQL执行语句入一个字符串中执行
- (void)executeStatementsTest{
NSString *sql =
@"CREATE TABLE IF NOT EXISTS t_student_tmp (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"
"INSERT INTO t_student_tmp (name, age) VALUES ('yixiang', 10);"
"INSERT INTO t_student_tmp (name, age) VALUES ('yixiangXX', 20);";
BOOL success = [_db executeStatements:sql];
if (success) {
NSLog(@"执行成功");
}else{
NSLog(@"执行失败");
} sql = @"SELECT * FROM t_student;"
"SELECT * FROM t_student_tmp;";
success = [_db executeStatements:sql withResultBlock:^int(NSDictionary *resultsDictionary) {
NSLog(@"%@",resultsDictionary);//查询结果都在resultsDictionary
return ;
}];
if (success) {
NSLog(@"查询成功");
}else{
NSLog(@"查询失败");
} }

2.1.6 加解密

在FMDatabase还有一个功能,就是对FMDB进行加解密处理,下面为实现方式

#pragma mark Key routines

- (BOOL)rekey:(NSString*)key {
NSData *keyData = [NSData dataWithBytes:(void *)[key UTF8String] length:(NSUInteger)strlen([key UTF8String])]; return [self rekeyWithData:keyData];
} - (BOOL)rekeyWithData:(NSData *)keyData {
#ifdef SQLITE_HAS_CODEC
if (!keyData) {
return NO;
} int rc = sqlite3_rekey(_db, [keyData bytes], (int)[keyData length]); if (rc != SQLITE_OK) {
NSLog(@"error on rekey: %d", rc);
NSLog(@"%@", [self lastErrorMessage]);
} return (rc == SQLITE_OK);
#else
#pragma unused(keyData)
return NO;
#endif
} - (BOOL)setKey:(NSString*)key {
NSData *keyData = [NSData dataWithBytes:[key UTF8String] length:(NSUInteger)strlen([key UTF8String])]; return [self setKeyWithData:keyData];
} - (BOOL)setKeyWithData:(NSData *)keyData {
#ifdef SQLITE_HAS_CODEC
if (!keyData) {
return NO;
} int rc = sqlite3_key(_db, [keyData bytes], (int)[keyData length]); return (rc == SQLITE_OK);
#else
#pragma unused(keyData)
return NO;
#endif
}

FMDB使用setKey:和 setKeyWithData:输入密码和鉴别身份,通过rekey:和rekeyWithData:来清除和密码和设置密码,在上面的源码大家可以发现,也是对sqlite3_key以及sqlite3_rekey函数的封装。

上面就是FMDB中FMDatabase类的主要核心代码,希望大家对FMDatabase认识有个提高。

2.2 FMResultSet

2.2.1 初始化对象

+ (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB;

通过上面方法可以看出里面有两个参数

(1)第一个参数:(FMStatement *)statement

该对象是对sqlite3_stmt的进一步封装,在sqlite3_stmt *所表示的内容已经不是我们经常使用过的sql语句啦,而是预处理过的语句。

(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]; // weak reference return FMDBReturnAutoreleased(rs);
}

2.2.2 遍历结果集合

- (BOOL)next;

FMDB通过- (BOOL)next函数完成遍历取结果集合。一起看一下- (BOOL)next;的实现代码

- (BOOL)next {
return [self nextWithError:nil];
}

上面代码可以发现:- (BOOL)next函数是对SQLite中-(BOOL)nextWithError:(NSError **)outErr函数的封装,主要完成对对象的逐行取值的任务。在深入看下nextWithError函数实现。

- (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.2.3 获取行列的值

通过查看源码发现有以下几处:

(1)- (int)intForColumnIndex:(int)columnIdx;

(2)- (long)longForColumnIndex:(int)columnIdx;

(3)- (long long int)longLongIntForColumnIndex:(int)columnIdx;

上面三个是根据列的索引获取该列的值。再看三个函数的实现代码

(1)- (int)intForColumnIndex:(int)columnIdx;

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

(2)- (long)longForColumnIndex:(int)columnIdx

- (long)longForColumnIndex:(int)columnIdx {
return (long)sqlite3_column_int64([_statement statement], columnIdx);
}

(3)- (long long int)longLongIntForColumnIndex:(int)columnIdx

- (long long int)longLongIntForColumnIndex:(int)columnIdx {
return sqlite3_column_int64([_statement statement], columnIdx);
}

通过上面三个函数,可以发现上面三个函数实际上是对sqlite3_column_ *函数的封装。

(4)- (int)intForColumn:(NSString*)columnName;

(5)- (long)longForColumn:(NSString*)columnName;

(6)- (long long int)longLongIntForColumn:(NSString*)columnName;

上面三个方法是根据列的名称取该列的值。下面看一下三个函数具体实现,只举一个即可,其他都是一样实现方式。

- (int)intForColumn:(NSString*)columnName {
return [self intForColumnIndex:[self columnIndexForName:columnName]];
}

再深入看一下intForColumnIndex实现方式,回到上面啦,(方法(1)看上)。

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

2.2.4 获取行中所有元素

- (NSDictionary*)resultDictionary:返回值类型为字典。下面是实现方式:

- (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.2.5 KVC讲解

- (void)kvcMagic:(id)object:FMDB中只能对string类型进行支持

下面是kvcMagic:(id)object实现方式

- (void)kvcMagic:(id)object {
// 使用了KVC,将数据库中的每一行数据对应到每一个对象中,对象的属性要和数据库的列名保持一直。
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)]];
}
}
}

2.3 FMDatabaseQueue

FMDB中比较突出优点就是对多线程的处理,而FMDB中对多线程的支持多亏FMDatabaseQueue类。

2.3.1 初始化队列

+ (instancetype)databaseQueueWithPath:(NSString*)aPath

实现代码如下:

+ (instancetype)databaseQueueWithPath:(NSString *)aPath {
FMDatabaseQueue *q = [[self alloc] initWithPath:aPath]; FMDBAutorelease(q); return q;
}

上面函数调用了- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName函数实现如下:

- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName {
self = [super init]; if (self != nil) { _db = [[[self class] databaseClass] databaseWithPath:aPath];
FMDBRetain(_db); #if SQLITE_VERSION_NUMBER >= 3005000
BOOL success = [_db openWithFlags:openFlags vfs:vfsName];
#else
BOOL success = [_db open];
#endif
if (!success) {
NSLog(@"Could not create database queue for path %@", aPath);
FMDBRelease(self);
return 0x00;
} _path = FMDBReturnRetained(aPath);
//生成一个串行队列
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
//给串行队列生成一个标识
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
_openFlags = openFlags;
_vfsName = [vfsName copy];
} return self;
}

2.3.2 串行执行数据库的操作

下面看一段代码

- (void)inDatabase:(void (^)(FMDatabase *db))block {
/* 使用dispatch_get_specific目的来查看当前queue是否是之前我们设定的那个_queue,如是的话,那么使用kDispatchQueueSpecificKey作为参数就这样传给dispatch_get_specific的话,返回的值是不为空,而且返回值应该就是上面initWithPath:函数中绑定的那个FMDatabaseQueue对象。有人说除了当前queue还有可能有其他什么queue?这就是FMDatabaseQueue的用途,你可以创建多个FMDatabaseQueue对象来并发执行不同的SQL语句。
另外为什么要判断是不是当前执行的这个queue呢?是为了防止死锁,防止多线程操作出现死锁!
*/
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock"); FMDBRetain(self); dispatch_sync(_queue, ^() {//串行执行block FMDatabase *db = [self database];
block(db); if ([db hasOpenResultSets]) {//调试代码
NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]"); #if defined(DEBUG) && DEBUG
NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
NSLog(@"query: '%@'", [rs query]);
}
#endif
}
}); FMDBRelease(self);
}

通过上面代码发现,对于一个queue就是一个串行队列,即使你开启多线程,依然是串行执行的。

如果大家对队列使用不是很熟悉,可以看一下以前博客可以帮助大家对这方面理解 https://www.cnblogs.com/guohai-stronger/p/9038567.html

为了方便大家理解,下面举一个例子:

/**
* FMDatabaseQueue实现多线程的案例
*/
- (void)FMDatabaseQueueMutilThreadTest{
//1、获取数据库文件的路径
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"]; //使用queue1
FMDatabaseQueue *queue1 = [FMDatabaseQueue databaseQueueWithPath:fileName]; [queue1 inDatabase:^(FMDatabase *db) {
for (int i=; i<; i++) {
NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]);
}
}]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
[queue1 inDatabase:^(FMDatabase *db) {
for (int i=; i<; i++) {
NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]);
}
}];
}); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
[queue1 inDatabase:^(FMDatabase *db) {
for (int i=; i<; i++) {
NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]);
}
}];
});
}

通过打印出来结果,看下:

通过上面发现,虽然开启了多个线程,依然是串行处理数据。

实际上:FMDatabaseQueue它通过内部创建了一个叫Serial的dispatch_queue_t来处理inDatabase和inTransaction传入的Blocks代码块。FMDatabaseQueue的目的是让我们避免发生并发访问数据库所造成的问题,因为我们对数据库的访问和操作是随机的,如果在里面内置一个Serial队列之后,FMDatabaseQueue这样就变成线程安全的了,达到了线程安全的目的。

对于同一个queue内部是串行执行,而对于不同的queue,它们是并发执行的。

2.3.3 关于事物

对于事物,在数据库中,也是保护数据库的安全一种方式。对于一条sql语句,要么全success,要么全fail。下面是实现代码:

- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
[self beginTransaction:NO withBlock:block];
}
- (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
FMDBRetain(self);
dispatch_sync(_queue, ^() { //串行执行,保证线程安全。 BOOL shouldRollback = NO; if (useDeferred) {
[[self database] beginDeferredTransaction];// 使用延时性事务
}
else {
[[self database] beginTransaction];// 默认使用独占性事务
} block([self database], &shouldRollback);//执行block if (shouldRollback) { //根据shouldRollback判断 判断是否回滚,还是提交。
[[self database] rollback];
}
else {
[[self database] commit];
}
}); FMDBRelease(self);
}

2.4 FMDatabaseAdditions

2.4.1 validateSQL

-(BOOL)validateSQL:(NSString*)sql error:(NSError**)error;通过此方法。校验sql语句是否合法。下面是实现代码

- (BOOL)validateSQL:(NSString*)sql error:(NSError**)error {
sqlite3_stmt *pStmt = NULL;
BOOL validationSucceeded = YES; int rc = sqlite3_prepare_v2([self sqliteHandle], [sql UTF8String], -, &pStmt, );
if (rc != SQLITE_OK) {
validationSucceeded = NO;
if (error) {
*error = [NSError errorWithDomain:NSCocoaErrorDomain
code:[self lastErrorCode]
userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage]
forKey:NSLocalizedDescriptionKey]];
}
} sqlite3_finalize(pStmt); return validationSucceeded;
}

2.4.2 其他

columnExists:判断column是否存在;

tableExists:判断表是否存在。

2.5 FMDatabasePool

对于FMDatabasePool,苹果本身并不建议使用,所以我们在这没有必要进行讲解,大家也可以不必追究其内容的实现。

上面就是FMDB源码解析的全部内容,大家主要对2.1,2.2,2.3内容进行详细看就可以啦,2.4和2.5了解即可。希望上面的FMDB源码讲解对大家对FMDB认识有所加强,欢迎大家指正。

我们已经讲完了SQLite和FMDB源码解析,下一篇我们将讲述SQLite和FMDB在使用上的区别。

FMDB源码解析的更多相关文章

  1. FMDB源码解析(上)-FMDB基本使用

    目录 一: 初识FMDB 二: 基本使用 三: 基本操作 结束 最后更新:2017-02-22 2017, 说到做到 一: 初识FMDB FMDB是iOS平台的SQLite数据库框架 FMDB以OC的 ...

  2. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  3. 【原】FMDB源码阅读(一)

    [原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  5. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  7. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  8. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  9. jQuery2.x源码解析(缓存篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...

随机推荐

  1. selenium自动化常用函数

    前段时间弄一个测试框架,满足公司简单网站的测试,整合了一个函数模块,包括常用的截图.邮件发送.测试报告生成,具体代码如下 import smtplib from BSTestRunner import ...

  2. 使用django我的第一个简单项目流程

    项目概述:本项目实现的是员工提交需要审批的事情给老板(例如请假事件.某些具体事务需要老板确认事件等),老板确认或者拒绝该事件,员工登录员工自己的页面可以查询响应的状态信息. 代码实现概略:需要创建两个 ...

  3. Freeradius服务器的搭建流程

    Freeradius服务器的搭建流程 一.服务器方面的配置 1 .安装radius服务器,数据库扩展插件 预先安装mysql数据库,然后安装freeradius,以及freeradius的数据库扩展插 ...

  4. 关于使用jquery.cookie.js存cookie中文出现乱码问题

    一.在Web开发中,有事为了页面之间传值,我们会用到cookie.但是当在cookie中存值为中文汉字时就会出现乱码! 这是一个简单例子: <!DOCTYPE html><html ...

  5. spak数据倾斜解决方案

    数据倾斜解决方案 数据倾斜的解决,跟之前讲解的性能调优,有一点异曲同工之妙. 性能调优中最有效最直接最简单的方式就是加资源加并行度,并注意RDD架构(复用同一个RDD,加上cache缓存).相对于前面 ...

  6. Hibernate知识总结(一)

    一.ORM ORM的全称是Object/Relation Mapping,即对象/关系映射,可以将其理解成一种规范,它概述了这类框架的基本特征:完成面向对象的编程语言到关系数据库的映射.可以把ORM看 ...

  7. 关于isNaN()函数的细节

    根据<JavaScript高级程序设计>的解释,NaN,即非数值(Not a Number),用于表示一个本来要返回数值的操作数未返回数值的情况,例如5/0就会得到NaN. 而因为NaN的 ...

  8. IDEA环境下GIT操作浅析之一Idea下仓库初始化与文件提交涉及到的基本命令

    目标总括 idea 下通过命令操作文件提交,删除,与更新并推送到github 开源库基本操作idea 下通过命令实现分支的创建与合并操作 idea 下通过图形化方式实现idea 项目版本控制基本操作 ...

  9. 超实用的 Nginx 极简教程,覆盖了常用场景

    概述 什么是 Nginx? Nginx (engine x) 是一款轻量级的 Web 服务器 .反向代理服务器及电子邮件(IMAP/POP3)代理服务器. 什么是反向代理? 反向代理(Reverse ...

  10. 微信小程序客服消息新增临时素材接口java实现

    今天想在微信小程序的客服信息窗口里回复用户一个图片信息,发现还需要上传素材,但是微信文档的上传临时素材接口写的模模糊糊,无奈去百度,网上清一色的PHP实现方式,难道我穿越了?PHP已经把java给超越 ...