上一篇博客講述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 > 0) { 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], -1, &pStmt, 0); 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 = 0; 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 > 0) { // 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] + 1]; FMDBRelease(statement); _isExecutingStatement = NO; return rs; }
發現上面那個函數有四個參數,看到源碼之后,我們一一講述四個參數:
(1)第一個參數sql:代表我們要查詢的sql語句;
(2)第二個參數arrayArgs:代表數組類型的參數,舉例如下:
FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?" withArgumentsInArray:@[@25]];
(3)第三個參數dictionaryArgs:代表字典類型的參數,舉例如下:
FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > :age" withParameterDictionary:@{@"age":@25}];
(4)第四個參數args:代表可變類型的參數,舉例如下:
FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?",@(20)];
上面是查詢基本源碼講解,大家可以詳細里面的源碼實現,里面也有講解。
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], -1, &pStmt, 0); 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 = 0; 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 > 0) { // 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] + 1]; 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 0; }]; 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 > 0) { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; int columnCount = sqlite3_column_count([_statement statement]); int columnIdx = 0; for (columnIdx = 0; 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 = 0; for (columnIdx = 0; 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=0; i<10; i++) { NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]); } }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [queue1 inDatabase:^(FMDatabase *db) { for (int i=11; i<20; i++) { NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]); } }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [queue1 inDatabase:^(FMDatabase *db) { for (int i=20; i<30; 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], -1, &pStmt, 0); 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在使用上的區別。