天天看點

FMDB源碼閱讀

轉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資料庫操作。

繼續閱讀