轉http://www.it165.net/pro/html/201602/61259.html
1. 前言
說實話,之前的SDWebImage和AFNetworking這兩個元件我還是使用過的,但是對于FMDB元件我是一點都沒用過。好在FMDB源碼中的main.m檔案提供了大量的示例,況且網上也有很多最佳實踐的例子,我就不在這獻醜了。我們先從一個最簡單的FMDB的例子開始:
// 找到使用者目錄下的Documents檔案夾位置 view source print ?
1.
NSString* docsdir = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
view source print ?
1.
// 将user.sqlite放到Documents檔案夾下,并生成user.sqlite的絕對路徑
2.
NSString* dbpath = [docsdir stringByAppendingPathComponent:@
'user.sqlite'
];
view source print ?
1.
// 根據user.sqlite的絕對路徑擷取到一個FMDatabase對象,其實就是一個封裝了的SQLite資料庫對象
2.
FMDatabase* db = [FMDatabase <strong>databaseWithPath</strong>:dbpath];
view source print ?
1.
// 打開該資料庫
2.
[db <strong>open</strong>];
view source print ?
1.
// 執行SQL語句 - select * from people
2.
FMResultSet *rs = [db
executeQuery view source print ?
1.
:@
'select * from people'
];
view source print ?
1.
// 利用next函數,循環輸出結果
2.
while
([rs <strong>next</strong>]) {
3.
NSLog(@
'%@ %@'
,
4.
[rs stringForColumn:@
'firstname'
],
5.
[rs stringForColumn:@
'lastname'
]);
6.
}
view source print ?
1.
// 關閉該資料庫
2.
[db <strong>close</strong>];
很簡單是吧,甚至我覺得上面我寫的注釋都多餘了。确實,FMDB說白了就是對SQLite資料庫的C/C++接口進行了一層封裝,當然功能也更為強大,比如多線程操作,另外FMDB接口要比原生的SQLite接口簡潔很多。下面我們就上面的例子研究下FMDB的基本流程。
2. FMDB的最基本流程(結合上面例子)
我們先看看上面代碼中我用藍色粗體高亮的部分,研究下其具體實作。
2.1 + [FMDatabase databaseWithPath:]
view source print ?
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.
3.
- (instancetype)initWithPath:(NSString*)aPath {
4.
<strong>
// SQLite支援三種線程模式,sqlite3_threadsafe()函數的傳回值可以确定編譯時指定的線程模式。</strong>
view source print ?
1.
<strong>
// 三種模式分别為1.單線程模式 2.多線程模式 3.串行模式 其中對于單線程模式,sqlite3_threadsafe()傳回false</strong>
view source print ?
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 source print ?
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 source print ?
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 >
) {
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 source print ?
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 ==
) {
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
;
30.
}
2.3 - [FMDatabase executeQuery:withArgumentsInArray:orDictionary:orVAList:](重點)
為什麼不講 - [FMDatabase executeQuery:]?因為- [FMDatabase executeQuery:]等等類似的函數,最終都是對- [FMDatabase executeQuery:withArgumentsInArray:orDictionary:orVAList:]的簡單封裝。該函數比較關鍵,主要是針對查詢的sql語句。
view source print ?
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,
);
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 =
;
59.
// 擷取到pStmt中需要綁定的參數個數
60.
int
queryCount = sqlite3_bind_parameter_count(pStmt);
// pointed out by Dominic Yu (thanks!)
61.
62.
// 舉一個使用dictionaryArgs的例子
view source print ?
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 >
) {
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.
034.
if
(arrayArgs && idx < (
int
)[arrayArgs count]) {
035.
obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
036.
}
037.
// 使用args的例子,使用args其實就是調用- (FMResultSet *)executeQuery:(NSString*)sql, ...;
038.
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.
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 source print ?
01.
// 傳回YES表示從資料庫中擷取到了下一行資料
02.
- (BOOL)nextWithError:(NSError **)outErr {
03.
// 嘗試步進到下一行
04.
int
rc = sqlite3_step([_statement statement]);
05.
06.
// 對傳回結果rc進行處理
07.
08.
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 source print ?
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 source print ?
1.
// 調用sqlite3_close來嘗試關閉資料庫
2.
rc = sqlite3_close(_db);
//如果目前資料庫上鎖,那麼就先嘗試重新關閉(置retry為YES) // 同時還嘗試釋放資料庫中的prepared語句資源 view source print ?
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 source print ?
1.
)表示從資料庫pDb中對應的pStmt語句開始一個個往下找出相應prepared語句,如果pStmt為nil,那麼就從pDb的第一個prepared語句開始。
view source print ?
1.
// 此處疊代找到資料庫中所有prepared語句,釋放其資源。
2.
while
((pStmt = sqlite3_next_stmt(_db, nil)) !=
) {
3.
NSLog(@
'Closing leaked statement'
);
4.
sqlite3_finalize(pStmt);
5.
retry = YES;
6.
}
7.
}
8.
}
view source print ?
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 source print ?
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 source print ?
1.
- (
void
)closeOpenResultSets {
2.
//Copy the set so we don't get mutation errors
3.
NSSet *openSetCopy = FMDBReturnAutoreleased([_openResultSets copy]);
view source print ?
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資料庫操作。