转自:http://www.it165.net/pro/html/201602/61259.html
1. 前言
说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于FMDB组件我是一点都没用过。好在FMDB源码中的main.m文件提供了大量的示例,况且网上也有很多最佳实践的例子,我就不在这献丑了。我们先从一个最简单的FMDB的例子开始:
// 找到用户目录下的Documents文件夹位置1.
NSString* docsdir = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
1.
// 将user.sqlite放到Documents文件夹下,并生成user.sqlite的绝对路径
2.
NSString* dbpath = [docsdir stringByAppendingPathComponent:@
'user.sqlite'
];
1.
// 根据user.sqlite的绝对路径获取到一个FMDatabase对象,其实就是一个封装了的SQLite数据库对象
2.
FMDatabase* db = [FMDatabase <strong>databaseWithPath</strong>:dbpath];
1.
// 打开该数据库
2.
[db <strong>open</strong>];
1.
// 执行SQL语句 - select * from people
2.
FMResultSet *rs = [db
1.
:@
'select * from people'
];
1.
// 利用next函数,循环输出结果
2.
while
([rs <strong>next</strong>]) {
3.
NSLog(@
'%@ %@'
,
4.
[rs stringForColumn:@
'firstname'
],
5.
[rs stringForColumn:@
'lastname'
]);
6.
}
1.
// 关闭该数据库
2.
[db <strong>close</strong>];
很简单是吧,甚至我觉得上面我写的注释都多余了。确实,FMDB说白了就是对SQLite数据库的C/C++接口进行了一层封装,当然功能也更为强大,比如多线程操作,另外FMDB接口要比原生的SQLite接口简洁很多。下面我们就上面的例子研究下FMDB的基本流程。
2. FMDB的最基本流程(结合上面例子)
我们先看看上面代码中我用蓝色粗体高亮的部分,研究下其具体实现。
2.1 + [FMDatabase databaseWithPath:]
01.
// 核心其实还是调用了+[FMDataBase initWithPath:]函数,下面会详解
02.
+ (instancetype)databaseWithPath:(NSString*)aPath {
03.
// FMDBReturnAutoReleased是为了让FMDB<strong>兼容MRC和ARC</strong>,具体细节看下其宏定义就明白了
04.
return
FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
05.
}
06.
07.
/** 初始化一个FMDataBase对象
08.
根据path(aPath)来创建一个SQLite数据库。对应的aPath参数有三种情形:
09.
10.
<strong>
1
. 数据库文件路径:不为空字符串,不为nil。如果该文件路径不存在,那么SQLite会给你新建一个</strong>
1.
<strong>
2
. 空字符串@
''
:将在外存临时给你创建一个空的数据库,并且如果该数据库连接释放,那么对应数据库会自动删除</strong>
1.
<strong>
3
. nil:会在内存中创建数据库,随着该数据库连接的释放,也会释放该数据库。</strong>
2.
*/
3.
- (instancetype)initWithPath:(NSString*)aPath {
4.
<strong>
// SQLite支持三种线程模式,sqlite3_threadsafe()函数的返回值可以确定编译时指定的线程模式。</strong>
1.
<strong>
// 三种模式分别为1.单线程模式 2.多线程模式 3.串行模式 其中对于单线程模式,sqlite3_threadsafe()返回false</strong>
01.
<strong>
// 对于另外两个模式,则返回true。这是因为单线程模式下没有进行互斥(mutex),所以多线程下是不安全的</strong>
02.
assert
(sqlite3_threadsafe());
03.
self = [
super
init];
04.
// 很多属性后面再提。不过这里值得注意的是_db居然赋值为nil,也就是说真正构建_db不是在initWithPath:这个函数中,这里透露下,其实作者是将构建部分代码放到了open函数中if (self) {
05.
_databasePath = [aPath copy];
06.
_openResultSets = [[NSMutableSet alloc] init];
07.
_db = nil;
08.
_logsErrors = YES;
09.
_crashOnErrors = NO;
10.
_maxBusyRetryTimeInterval =
2
;
11.
}
12.
13.
return
self;
14.
}
2.2 - [FMDatabase open]
上面提到过+ [FMDatabase databaseWithPath:]和- [FMDatabase initWithPath:]本质上只是给了数据库一个名字,并没有真实创建或者获取数据库。这里的open函数才是真正获取到数据库,其本质上也就是调用SQLite的C/C++接口 – sqlite3_open()。
sqlite3_open(const char *filename, sqlite3 **ppDb)
该例程打开一个指向 SQLite 数据库文件的连接,返回一个用于其他 SQLite 程序的数据库连接对象。
如果 filename 参数是 NULL 或 ':memory:',那么 sqlite3_open() 将会在 RAM 中创建一个内存数据库,这只会在 session 的有效时间内持续。
如果文件名 filename 不为 NULL,那么 sqlite3_open() 将使用这个参数值尝试打开数据库文件。如果该名称的文件不存在,sqlite3_open() 将创建一个新的命名为该名称的数据库文件并打开。
01.
- (BOOL)open {
02.
if
(_db) {
03.
return
YES;
04.
}
05.
06.
int
err = sqlite3_open([self sqlitePath], (sqlite3**)&_db );
07.
if
(err != SQLITE_OK) {
08.
NSLog(@
'error opening!: %d'
, err);
09.
return
NO;
10.
}
11.
// 若_maxBusyRetryTimeInterval大于0,那么就调用setMaxBusyRetryTimeInterval:函数
12.
// setMaxBusyRetryTimeInterval:函数主要是调用sqlite3_busy_handler来处理其他线程已经在操作数据库的情况,默认_maxBusyRetryTimeInterval为2。
01.
// 具体该参数有什么用,下面在FMDBDatabaseBusyHandler函数中会详解。
02.
if
(_maxBusyRetryTimeInterval >
0.0
) {
03.
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
04.
}
05.
06.
return
YES;
07.
}
08.
09.
- (
void
)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout {
10.
11.
_maxBusyRetryTimeInterval = timeout;
12.
13.
if
(!_db) {
14.
return
;
15.
}
16.
// 处理的handler设置为FMDBDatabaseBusyHandler这个函数
17.
if
(timeout >
0
) {
18.
sqlite3_busy_handler(_db, &FMDBDatabaseBusyHandler, (__bridge
void
*)(self));
19.
}
20.
else
{
21.
// 不使用任何busy handler处理
22.
sqlite3_busy_handler(_db, nil, nil);
23.
}
24.
}
这里需要提一下sqlite3_busy_handler这个函数:
int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);
第一个参数是告知哪个数据库需要设置busy handler。
第二个参数是其实就是回调函数(busy handler)了,当你调用该回调函数时,需传递给它的一个void*的参数的拷贝,也即sqlite3_busy_handler的第三个参数;另一个需要传给回调函数的int参数是表示这次锁事件,该回调函数被调用的次数。如果回调函数返回0时,将不再尝试再次访问数据库而返回SQLITE_BUSY或者SQLITE_IOERR_BLOCKED。如果回调函数返回非0, 将会不断尝试操作数据库。
总结:程序运行过程中,如果有其他进程或者线程在读写数据库,那么sqlite3_busy_handler会不断调用回调函数,直到其他进程或者线程释放锁。获得锁之后,不会再调用回调函数,从而向下执行,进行数据库操作。该函数是在获取不到锁的时候,以执行回调函数的次数来进行延迟,等待其他进程或者线程操作数据库结束,从而获得锁操作数据库。
大家也看出来了,sqlite3_busy_handler函数的关键就是这个回调函数了,此处作者定义的是一个名叫FMDBDatabaseBusyHandler的函数作为其busy handler。
01.
// 注意:appledoc(生成文档的软件)中,对于有具体实现的C函数,比如下面这个函数,
02.
// 是有bug的。所以你在生成文档时,忽略.m文件。
03.
04.
// 该函数就是简单调用sqlite3_sleep来挂起进程
05.
static
int
FMDBDatabaseBusyHandler(
void
*f,
int
count) {
06.
FMDatabase *self = (__bridge FMDatabase*)f;
07.
// 如果count为0,表示的第一次执行回调函数
08.
// 初始化self->_startBusyRetryTime,供后面计算delta使用
09.
if
(count ==
0
) {
10.
self->_startBusyRetryTime = [NSDate timeIntervalSinceReferenceDate];
11.
return
1
;
12.
}
13.
// 使用delta变量控制执行回调函数的次数,每次挂起50~100ms
14.
// 所以<strong>maxBusyRetryTimeInterval</strong>的作用就在这体现出来了
15.
// 当挂起的时长大于maxBusyRetryTimeInterval,就返回0,并停止执行该回调函数了
16.
NSTimeInterval delta = [NSDate timeIntervalSinceReferenceDate] - (self->_startBusyRetryTime);
17.
18.
if
(delta < [self maxBusyRetryTimeInterval]) {
19.
// 使用sqlite3_sleep每次当前线程挂起50~100ms
20.
int
requestedSleepInMillseconds = (
int
) arc4random_uniform(
50
) +
50
;
21.
int
actualSleepInMilliseconds = sqlite3_sleep(requestedSleepInMillseconds);
22.
// 如果实际挂起的时长与想要挂起的时长不一致,可能是因为构建SQLite时没将HAVE_USLEEP置为1
23.
if
(actualSleepInMilliseconds != requestedSleepInMillseconds) {
24.
NSLog(@
'WARNING: Requested sleep of %i milliseconds, but SQLite returned %i. Maybe SQLite wasn'
t built with HAVE_USLEEP=
1
?', requestedSleepInMillseconds, actualSleepInMilliseconds);
25.
}
26.
return
1
;
27.
}
28.
29.
return
0
;
30.
}
2.3 - [FMDatabase executeQuery:withArgumentsInArray:orDictionary:orVAList:](重点)
为什么不讲 - [FMDatabase executeQuery:]?因为- [FMDatabase executeQuery:]等等类似的函数,最终都是对- [FMDatabase executeQuery:withArgumentsInArray:orDictionary:orVAList:]的简单封装。该函数比较关键,主要是针对查询的sql语句。
01.
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
02.
// 判断当前是否存在数据库以供操作
03.
if
(![self databaseExists]) {
04.
return
0x00
;
05.
}
06.
// 如果当前线程已经在使用数据库了,那就输出正在使用的警告
07.
if
(_isExecutingStatement) {
08.
[self warnInUse];
09.
return
0x00
;
10.
}
11.
12.
_isExecutingStatement = YES;
13.
14.
int
rc =
0x00
;
15.
sqlite3_stmt *pStmt =
0x00
;
// sqlite的prepared语句类型
16.
FMStatement *statement =
0x00
;
// 对sqlite3_stmt的简单封装,在实际应用中,你不应直接操作FMStatement对象
17.
FMResultSet *rs =
0x00
;
// FMResultSet对象是用来获取最终查询结果的
18.
// 需要追踪sql执行状态的话,输出执行状态
19.
if
(_traceExecution && sql) {
20.
NSLog(@
'%@ executeQuery: %@'
, self, sql);
21.
}
22.
// 调用sql语句之前,首先要将sql字符串预处理一下,转化为SQLite可用的prepared语句(预处理语句)
23.
// <strong>使用sqlite3_prepare_v2来生成sql对应的prepare语句(即pStmt)代价很大</strong>
24.
// 所以建议使用缓存机制来减少对sqlite3_prepare_v2的使用
25.
if
(_shouldCacheStatements) {
26.
// 获取到缓存中的prepared语句
27.
statement = [self cachedStatementForQuery:sql];
28.
pStmt = statement ? [statement statement] :
0x00
;
29.
// prepared语句可以被重置(调用sqlite3_reset函数),然后可以重新绑定参数以便重新执行。
30.
[statement reset];
31.
}
32.
// 如果缓存中没有sql对应的prepared语句,那么只能使用sqlite3_prepare_v2函数进行预处理
33.
if
(!pStmt) {
34.
35.
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -
1
, &pStmt,
0
);
36.
// 如果生成prepared语句出错,那么就根据是否需要打印错误信息(_logsErrors)以及是否遇到错误直接中止程序执行(_crashOnErrors)来执行出错处理。
37.
// 最后调用<strong>sqlite3_finalize函数释放所有的内部资源和sqlite3_stmt数据结构,有效删除prepared语句</strong>。
38.
if
(SQLITE_OK != rc) {
39.
if
(_logsErrors) {
40.
NSLog(@
'DB Error: %d '
%@
''
, [self lastErrorCode], [self lastErrorMessage]);
41.
NSLog(@
'DB Query: %@'
, sql);
42.
NSLog(@
'DB Path: %@'
, _databasePath);
43.
}
44.
45.
if
(_crashOnErrors) {
46.
NSAssert(
false
, @
'DB Error: %d '
%@
''
, [self lastErrorCode], [self lastErrorMessage]);
47.
// abort()函数表示中止程序执行,直接从调用的地方跳出。
48.
abort();
49.
}
50.
51.
sqlite3_finalize(pStmt);
52.
_isExecutingStatement = NO;
53.
return
nil;
54.
}
55.
}
56.
57.
id obj;
58.
int
idx =
0
;
59.
// 获取到pStmt中需要绑定的参数个数
60.
int
queryCount = sqlite3_bind_parameter_count(pStmt);
// pointed out by Dominic Yu (thanks!)
61.
62.
// 举一个使用dictionaryArgs的例子
1.
NSMutableDictionary
001.
if
(dictionaryArgs) {
002.
003.
for
(NSString *dictionaryKey in [dictionaryArgs allKeys]) {
004.
005.
// 在每个dictionaryKey之前加上冒号,比如上面的a -> :a,方便获取参数在prepared语句中的索引
006.
NSString *parameterName = [[NSString alloc] initWithFormat:@
':%@'
, dictionaryKey];
007.
// 查看执行状况
008.
if
(_traceExecution) {
009.
NSLog(@
'%@ = %@'
, parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
010.
}
011.
012.
// 在prepared语句中查找对应parameterName的参数索引值namedIdx
013.
int
namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
014.
015.
FMDBRelease(parameterName);
016.
// 可以利用索引namedIdx获取对应参数,再使用bindObject:函数将dictionaryArgs保存的value绑定给对应参数
017.
if
(namedIdx >
0
) {
018.
[self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
019.
// 使用这个idx来判断sql中的所有参数值是否都绑定上了
020.
idx++;
021.
}
022.
else
{
023.
NSLog(@
'Could not find index for %@'
, dictionaryKey);
024.
}
025.
}
026.
}
027.
else
{
028.
029.
while
(idx < queryCount) {
030.
// 使用arrayArgs的例子
031.
/**
032.
[db executeQuery:@'insert into testOneHundredTwelvePointTwo values (?, ?)' withArgumentsInArray:[NSArray arrayWithObjects:@'one', [NSNumber numberWithInteger:2], nil]];
033.
*/
034.
if
(arrayArgs && idx < (
int
)[arrayArgs count]) {
035.
obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
036.
}
037.
// 使用args的例子,使用args其实就是调用- (FMResultSet *)executeQuery:(NSString*)sql, ...;
038.
/**
039.
FMResultSet *rs = [db executeQuery:@'select rowid,* from test where a = ?', @'hi''];
040.
*/
041.
else
if
(args) {
042.
obj = va_arg(args, id);
043.
}
044.
else
{
045.
break
;
046.
}
047.
048.
if
(_traceExecution) {
049.
if
([obj isKindOfClass:[NSData
class
]]) {
050.
NSLog(@
'data: %ld bytes'
, (unsigned
long
)[(NSData*)obj length]);
051.
}
052.
else
{
053.
NSLog(@
'obj: %@'
, obj);
054.
}
055.
}
056.
057.
idx++;
058.
// 绑定参数值
059.
[self bindObject:obj toColumn:idx inStatement:pStmt];
060.
}
061.
}
062.
// 如果绑定的参数数目不对,认为出错,并释放资源
063.
if
(idx != queryCount) {
064.
NSLog(@
'Error: the bind count is not correct for the # of variables (executeQuery)'
);
065.
sqlite3_finalize(pStmt);
066.
_isExecutingStatement = NO;
067.
return
nil;
068.
}
069.
070.
FMDBRetain(statement);
// to balance the release below
071.
// statement不为空,进行缓存
072.
if
(!statement) {
073.
statement = [[FMStatement alloc] init];
074.
[statement setStatement:pStmt];
075.
// 使用sql作为key来缓存statement(即sql对应的prepare语句)
076.
if
(_shouldCacheStatements && sql) {
077.
[self setCachedStatement:statement forQuery:sql];
078.
}
079.
}
080.
081.
// 根据statement和self(FMDatabase对象)构建一个FMResultSet对象,此函数中仅仅是构建该对象,还没使用next等函数获取查询结果
082.
// 注意FMResultSet中含有以下成员(除了最后一个,其他成员均在此处初始化过了)
083.
/**
084.
@interface FMResultSet : NSObject {
085.
FMDatabase *_parentDB; // 表示该对象查询的数据库,主要是为了能在FMResultSet自己的函数中索引到正在操作的FMDatabase对象
086.
FMStatement *_statement; // prepared语句
087.
088.
NSString *_query; // 对应的sql查询语句
089.
NSMutableDictionary *_columnNameToIndexMap;
090.
}
091.
*/
092.
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
093.
[rs setQuery:sql];
094.
// 将此时的FMResultSet对象添加_openResultSets,主要是为了调试
095.
NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
096.
[_openResultSets addObject:openResultSet];
097.
// 并设置statement的使用数目useCount加1,暂时不清楚此成员有何作用,感觉也是用于调试
098.
[statement setUseCount:[statement useCount] +
1
];
099.
100.
FMDBRelease(statement);
101.
// 生成statement的操作已经结束
102.
_isExecutingStatement = NO;
103.
104.
return
rs;
105.
}
2.4 - [FMResultSet nextWithError:]
- [FMResultSet next]函数其实就是对nextWithError:的简单封装。作用就是从我们上一步open中获取到的FMResultSet对象中读取查询后结果的每一行,交给用户自己处理。读取每一行的方法(即next)其实就是封装了sqlite3_step函数。而nextWithError:主要封装了对sqlite3_step函数返回结果的处理。
int sqlite3_step(sqlite3_stmt*);
sqlite3_prepare函数将SQL命令字符串解析并转换为一系列的命令字节码,这些字节码最终被传送到SQlite3的虚拟数据库引擎(VDBE: Virtual Database Engine)中执行,完成这项工作的是sqlite3_step函数。比如一个SELECT查询操作,sqlite3_step函数的每次调用都会返回结果集中的其中一行,直到再没有有效数据行了。每次调用sqlite3_step函数如果返回SQLITE_ROW,代表获得了有效数据行,可以通过sqlite3_column函数提取某列的值。如果调用sqlite3_step函数返回SQLITE_DONE,则代表prepared语句已经执行到终点了,没有有效数据了。很多命令第一次调用sqlite3_step函数就会返回SQLITE_DONE,因为这些SQL命令不会返回数据。对于INSERT,UPDATE,DELETE命令,会返回它们所修改的行号——一个单行单列的值。
01.
// 返回YES表示从数据库中获取到了下一行数据
02.
- (BOOL)nextWithError:(NSError **)outErr {
03.
// 尝试步进到下一行
04.
int
rc = sqlite3_step([_statement statement]);
05.
06.
// 对返回结果rc进行处理
07.
08.
/**
09.
SQLITE_BUSY 数据库文件有锁
10.
SQLITE_LOCKED 数据库中的某张表有锁
11.
SQLITE_DONE sqlite3_step()执行完毕
12.
SQLITE_ROW sqlite3_step()获取到下一行数据
13.
SQLITE_ERROR 一般用于没有特别指定错误码的错误,就是说函数在执行过程中发生了错误,但无法知道错误发生的原因。
14.
SQLITE_MISUSE 没有正确使用SQLite接口,比如一条语句在sqlite3_step函数执行之后,没有被重置之前,再次给其绑定参数,这时bind函数就会返回SQLITE_MISUSE。
15.
*/
16.
if
(SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
17.
NSLog(@
'%s:%d Database busy (%@)'
, __FUNCTION__, __LINE__, [_parentDB databasePath]);
18.
NSLog(@
'Database busy'
);
19.
if
(outErr) {
20.
// lastError使用sqlite3_errcode获取到错误码,封装成NSError对象返回
21.
*outErr = [_parentDB lastError];
22.
}
23.
}
24.
else
if
(SQLITE_DONE == rc || SQLITE_ROW == rc) {
25.
// all is well, let's return.
26.
}
27.
else
if
(SQLITE_ERROR == rc) {
28.
// sqliteHandle就是获取到对应FMDatabase对象,然后使用sqlite3_errmsg来获取错误码的字符串
29.
NSLog(@
'Error calling sqlite3_step (%d: %s) rs'
, rc, sqlite3_errmsg([_parentDB sqliteHandle]));
30.
if
(outErr) {
31.
*outErr = [_parentDB lastError];
32.
}
33.
}
34.
else
if
(SQLITE_MISUSE == rc) {
35.
// uh oh.
36.
NSLog(@
'Error calling sqlite3_step (%d: %s) rs'
, rc, sqlite3_errmsg([_parentDB sqliteHandle]));
37.
if
(outErr) {
38.
if
(_parentDB) {
39.
*outErr = [_parentDB lastError];
40.
}
41.
else
{
42.
// 如果next和nextWithError函数是在当前的FMResultSet关闭之后调用的
43.
// 这时输出的错误信息应该是parentDB不存在
44.
NSDictionary* errorMessage = [NSDictionary dictionaryWithObject:@
'parentDB does not exist'
forKey:NSLocalizedDescriptionKey];
45.
*outErr = [NSError errorWithDomain:@
'FMDatabase'
code:SQLITE_MISUSE userInfo:errorMessage];
46.
}
47.
48.
}
49.
}
50.
else
{
51.
// wtf?
52.
NSLog(@
'Unknown error calling sqlite3_step (%d: %s) rs'
, rc, sqlite3_errmsg([_parentDB sqliteHandle]));
53.
if
(outErr) {
54.
*outErr = [_parentDB lastError];
55.
}
56.
}
57.
58.
// 如果不是读取下一行数据,那么就关闭数据库
59.
if
(rc != SQLITE_ROW) {
60.
[self close];
61.
}
62.
63.
return
(rc == SQLITE_ROW);
64.
}
2.5 - [FMDatabase close]
与open函数成对调用。主要还是封装了sqlite_close函数。
01.
- (BOOL)close {
02.
// 清除缓存的prepared语句,下面会详解
03.
[self clearCachedStatements];
04.
// 关闭所有打开的FMResultSet对象,目前看来这个_openResultSets大概也是用来调试的
05.
[self closeOpenResultSets];
06.
07.
if
(!_db) {
08.
return
YES;
09.
}
10.
11.
int
rc;
12.
BOOL retry;
13.
BOOL triedFinalizingOpenStatements = NO;
14.
15.
do
{
16.
retry = NO;
1.
// 调用sqlite3_close来尝试关闭数据库
2.
rc = sqlite3_close(_db);
1.
if
(SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
2.
if
(!triedFinalizingOpenStatements) {
3.
triedFinalizingOpenStatements = YES;
4.
sqlite3_stmt *pStmt;
1.
)表示从数据库pDb中对应的pStmt语句开始一个个往下找出相应prepared语句,如果pStmt为nil,那么就从pDb的第一个prepared语句开始。
1.
// 此处迭代找到数据库中所有prepared语句,释放其资源。
2.
while
((pStmt = sqlite3_next_stmt(_db, nil)) !=
0
) {
3.
NSLog(@
'Closing leaked statement'
);
4.
sqlite3_finalize(pStmt);
5.
retry = YES;
6.
}
7.
}
8.
}
01.
// 关闭出错,输出错误码
02.
else
if
(SQLITE_OK != rc) {
03.
NSLog(@
'error closing!: %d'
, rc);
04.
}
05.
}
06.
while
(retry);
07.
08.
_db = nil;
09.
return
YES;
10.
}
11.
12.
// _cachedStatements是用来缓存prepared语句的,所以清空_cachedStatements就是将每个缓存的prepared语句释放
01.
// 具体实现就是使用下面那个close函数,close函数中调用了sqlite_finalize函数释放资源
02.
- (
void
)clearCachedStatements {
03.
04.
for
(NSMutableSet *statements in [_cachedStatements objectEnumerator]) {
05.
// <strong>makeObjectsPerformSelector会并发执行同一件事,所以效率比for循环一个个执行要快很多</strong>
06.
[statements makeObjectsPerformSelector:
@selector
(close)];
07.
}
08.
09.
[_cachedStatements removeAllObjects];
10.
}
11.
// 注意:此为FMResultSet的close函数
12.
- (
void
)close {
13.
if
(_statement) {
14.
sqlite3_finalize(_statement);
15.
_statement =
0x00
;
16.
}
17.
18.
_inUse = NO;
19.
}
1.
- (
void
)closeOpenResultSets {
2.
//Copy the set so we don't get mutation errors
3.
NSSet *openSetCopy = FMDBReturnAutoreleased([_openResultSets copy]);
01.
// 迭代关闭_openResultSets中的FMResultSet对象
02.
for
(NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
03.
FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
04.
// 清除FMResultSet的操作
05.
[rs setParentDB:nil];
06.
[rs close];
07.
08.
[_openResultSets removeObject:rsInWrappedInATastyValueMeal];
09.
}
10.
}
3. 总结
本文结合一个基本的FMDB使用案例,介绍了FMDB基本的运作流程和内部实现。总的来说,FMDB就是对SQLite的封装,所以学习FMDB根本还是在学习SQLite数据库操作。