您的位置:首页 > 其它

FMDB源码阅读

2016-03-28 12:46 190 查看
转http://www.it165.net/pro/html/201602/61259.html


1. 前言

说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于FMDB组件我是一点都没用过。好在FMDB源码中的main.m文件提供了大量的示例,况且网上也有很多最佳实践的例子,我就不在这献丑了。我们先从一个最简单的FMDB的例子开始:
// 找到用户目录下的Documents文件夹位置

view
sourceprint?

1.
NSString*
docsdir = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) lastObject];


view
sourceprint?

1.
//
将user.sqlite放到Documents文件夹下,并生成user.sqlite的绝对路径


2.
NSString*
dbpath = [docsdir stringByAppendingPathComponent:@
'user.sqlite'
];


view
sourceprint?

1.
//
根据user.sqlite的绝对路径获取到一个FMDatabase对象,其实就是一个封装了的SQLite数据库对象


2.
FMDatabase*
db = [FMDatabase <strong>databaseWithPath</strong>:dbpath];


view
sourceprint?

1.
//
打开该数据库


2.
[db
<strong>open</strong>];


view
sourceprint?

1.
//
执行SQL语句 - select * from people


2.
FMResultSet
*rs = [db


executeQuery

view
sourceprint?

1.
:@
'select
* from people'
];


view
sourceprint?

1.
//
利用next函数,循环输出结果


2.
while
([rs
<strong>next</strong>]) {


3.
NSLog(@
'%@
%@'
,


4.
[rs
stringForColumn:@
'firstname'
],


5.
[rs
stringForColumn:@
'lastname'
]);


6.
}


view
sourceprint?

1.
//
关闭该数据库


2.
[db
<strong>close</strong>];


很简单是吧,甚至我觉得上面我写的注释都多余了。确实,FMDB说白了就是对SQLite数据库的C/C++接口进行了一层封装,当然功能也更为强大,比如多线程操作,另外FMDB接口要比原生的SQLite接口简洁很多。下面我们就上面的例子研究下FMDB的基本流程。


2. FMDB的最基本流程(结合上面例子)

我们先看看上面代码中我用蓝色粗体高亮的部分,研究下其具体实现。


2.1 +[FMDatabase databaseWithPath:]

view
sourceprint?

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>


view
sourceprint?

1.
<strong>
2
.
空字符串@
''
:将在外存临时给你创建一个空的数据库,并且如果该数据库连接释放,那么对应数据库会自动删除</strong>


view
sourceprint?

1.
<strong>
3
.
nil:会在内存中创建数据库,随着该数据库连接的释放,也会释放该数据库。</strong>


2.
*/


3.
-
(instancetype)initWithPath:(NSString*)aPath {


4.
<strong>
//
SQLite支持三种线程模式,sqlite3_threadsafe()函数的返回值可以确定编译时指定的线程模式。</strong>


view
sourceprint?

1.
<strong>
//
三种模式分别为1.单线程模式 2.多线程模式 3.串行模式 其中对于单线程模式,sqlite3_threadsafe()返回false</strong>


view
sourceprint?

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() 将创建一个新的命名为该名称的数据库文件并打开。

view
sourceprint?

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。


view
sourceprint?

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。

view
sourceprint?

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语句。

view
sourceprint?

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的例子


/**

view
sourceprint?

1.
NSMutableDictionary


*dictionaryArgs = [NSMutableDictionary dictionary];[dictionaryArgs setObject:@'Text1' forKey:@'a'];[db executeQuery:@'select * from namedparamcounttest
where a = :a' withParameterDictionary:dictionaryArgs];// 注意类似:AAA前面有冒号的就是参数 // 其他的参数形式如:'?', '?NNN', ':AAA', '$AAA', 或 '@AAA' */

view
sourceprint?

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命令,会返回它们所修改的行号——一个单行单列的值。

view
sourceprint?

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函数。

view
sourceprint?

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;


view
sourceprint?

1.
//
调用sqlite3_close来尝试关闭数据库


2.
rc
=sqlite3_close(_db);


//如果当前数据库上锁,那么就先尝试重新关闭(置retry为YES) // 同时还尝试释放数据库中的prepared语句资源

view
sourceprint?

1.
if
(SQLITE_BUSY
== rc || SQLITE_LOCKED == rc) {


2.
if
(!triedFinalizingOpenStatements)
{


3.
triedFinalizingOpenStatements
= YES;


4.
sqlite3_stmt
*pStmt;


// sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt

view
sourceprint?

1.
)表示从数据库pDb中对应的pStmt语句开始一个个往下找出相应prepared语句,如果pStmt为nil,那么就从pDb的第一个prepared语句开始。


view
sourceprint?

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.
}


view
sourceprint?

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语句释放


view
sourceprint?

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.
}


// 清除_openResultSets

view
sourceprint?

1.
-
(
void
)closeOpenResultSets
{


2.
//Copy
the set so we don't get mutation errors


3.
NSSet
*openSetCopy = FMDBReturnAutoreleased([_openResultSets copy]);


view
sourceprint?

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数据库操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: