如上一章所講,FMDB源碼主要有如下幾個文件組成:面試
FMResultSet : 表示FMDatabase執行查詢以後的結果集。sql
FMDatabase : 表示一個單獨的SQLite數據庫操做實例,經過它能夠對數據庫進行增刪改查等等操做。數據庫
FMDatabaseAdditions : 擴展FMDatabase類,新增對查詢結果只返回單個值的方法進行簡化,對錶、列是否存在,版本號,校驗SQL等等功能。數組
FMDatabaseQueue : 使用串行隊列 ,對多線程的操做進行了支持。緩存
FMDatabasePool : 使用任務池的形式,對多線程的操做提供支持。(不過官方對這種方式並不推薦使用,優先選擇FMDatabaseQueue的方式:ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD)bash
這一篇咱們就來說講FMDatabase和FMDatabaseAdditions的實現思路。網絡
這是一個個人iOS交流羣:624212887,羣文件自行下載,無論你是小白仍是大牛熱烈歡迎進羣 ,分享面試經驗,討論技術, 你們一塊兒交流學習成長!但願幫助開發者少走彎路。——點擊:加入多線程
-(BOOL)open;實際上是對sqlite3_open()函數的封裝。函數
- (BOOL)open {
if (_db) {
return YES;
}
int err = sqlite3_open([self sqlitePath], (sqlite3**)&_db );
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
}
//當執行這段代碼的時候,數據庫正在被其餘線程訪問,那咱們就須要給他設置一個重試時間,默認爲2秒。
if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
}
return YES;
}
- (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設置重試時間,其實調用的是源碼分析
int sqlite3_busy_handler(sqlite3
該函數的第一個參數爲:須要告知哪個數據庫須要設置busy handler。
第二個參數是須要回調的busy handler,當你調用該回調函數的時候,須要傳遞給它一個void*的參數拷貝,也就是sqlite3_busy_handler的第三個參數。
另外一個須要傳給回調函數的int參數表示此次鎖事件,該回調函數被調用的次數。
若是回調函數返回0時,將再也不嘗試再次訪問數據庫而返回SQLITE_BUSY或者SQLITE_IOERR_BLOCKED.若是回調函數返回非0,將會不斷嘗試操做數據困。
程序運行過程當中,若是有其餘進程或者線程在讀寫數據庫,那麼sqlite3_busy_handler會不斷調用該回調函數,直到其餘線程或者進程釋放鎖。得到鎖以後,不會再調用該回調函數,從而繼續向下執行下去,進行數據庫操做。該函數是在獲取不到鎖的時候,以執行回調函數的次數來進行延時,等待其餘進程或者線程操做數據庫結束,從而得到鎖進行操做數據庫。
executeQuery系列函數從根本上看其實調用的都是
-(FMResultSet )executeQuery:(NSString )sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args。
參數sql : 須要查詢的sql語句。
參數arrayArgs : 數組類型的參數。應于于
FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?" withArgumentsInArray:@[@25]];
複製代碼
參數dictionaryArgs:字典類型的參數。應用於
FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > :age" withParameterDictionary:@{@"age":@25}];
複製代碼
參數args:可變參數類型。應用於
FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?",@(20)];
複製代碼
下面來看一下該函數的大體實現過程,主要就是對sqlite3_prepare_v2的封裝。
- (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;
if (_traceExecution && sql) {//打印sql語句
NSLog(@"%@ executeQuery: %@", self, sql);
}
if (_shouldCacheStatements) {//獲取緩存數據
statement = [self cachedStatementForQuery:sql];
pStmt = statement ? [statement statement] : 0x00;
[statement reset];
}
if (!pStmt) {//沒有緩存數據,直接查詢數據庫
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);//對sql語句進行預處理,生成預處理過的「sql語句」pStmt。
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!)//queryCount獲取參數個數,「?」或者「:age」都算。
// If dictionaryArgs is passed in, that means we are using sqlite's named parameter support if (dictionaryArgs) {//對dictionaryArgs參數的處理,相似於":age"參數形式 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. // * 將通配符?:age按照索引 賦值爲obj。 //* SELECT * FROM t_student WHERE age > :age --> SELECT * FROM t_student WHERE age > obj [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++; // * 將通配符?:age按照索引 賦值爲obj。 //* SELECT * FROM t_student WHERE age > ? --> SELECT * FROM t_student WHERE age > obj [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]; if (_shouldCacheStatements && sql) {//緩存處理,key爲sql語句,值爲statement [self setCachedStatement:statement forQuery:sql]; } } // the statement gets closed in rs's dealloc or [rs close];
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];//生成FMResultSet結果集對象
[rs setQuery:sql];
NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
[_openResultSets addObject:openResultSet];
[statement setUseCount:[statement useCount] + 1];
FMDBRelease(statement);
_isExecutingStatement = NO;
return rs;
}
複製代碼
上面有一個關鍵的函數就是- (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt進行參數綁定,把通配符形式的參數改爲obj實際參數。
將通配符?:age按照索引 賦值爲obj。好比:SELECT * FROM t_student WHERE age > :age –> SELECT * FROM t_student WHERE age > obj 或者 SELECT * FROM t_student WHERE age > ? –> SELECT * FROM t_student WHERE age > obj
這裏的更新,並不僅是單單的更新數據。而是對數據庫有更改的操做,增刪改都算。FMDB調用的都是executeUpdate系列函數。這個函數基本上跟executeQuery系列函數的實現基本差很少。只是它生成statement對象後,直接調用rc = sqlite3_step(pStmt);更新執行,而沒有像executeQuery延遲到FMResultSet中的next函數中執行。
好比:FMResultSet resultSet = [_db executeQueryWithFormat:@」SELECT FROM t_student WHERE age > %d」,20];替換原來?通配符。
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... {
va_list args;
va_start(args, format);
NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]];
NSMutableArray *arguments = [NSMutableArray array];
[self extractSQL:format argumentsList:args intoString:sql arguments:arguments];
va_end(args);
return [self executeQuery:sql withArgumentsInArray:arguments];
}
複製代碼
從代碼中能夠看出,這裏實際上是使用
/**
* 將format的sql語句 改成 可用的sql語句
* SELECT * FROM t_student WHERE age > %d --> SELECT * FROM t_student WHERE age > ?
*
* @param sql SELECT * FROM t_student WHERE age > %d
* @param args 可變參數,c語言的格式化參數
* @param cleanedSQL SELECT * FROM t_student WHERE age > ?
* @param arguments oc數組
*/
- (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments;
複製代碼
使用executeStatements函數能夠一次性執行多條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(@"查詢失敗");
}
}
複製代碼
它又是如何實現的呢?他其實就是對sqlite3_exec函數的封裝。
- (BOOL)executeStatements:(NSString *)sql withResultBlock:(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);
}
複製代碼
FMDB中使用- [FMDatabase setKey:]和- [FMDatabase setKeyWithData:]輸入數據庫密碼以求驗證用戶身份,使用- [FMDatabase rekey:]和- [FMDatabase rekeyWithData:]來給數據庫設置密碼或者清除密碼。這兩類函數分別對sqlite3_key和sqlite3_rekey函數進行了封裝。
擴展FMDatabase類,新增對查詢結果只返回單個值的方法進行簡化,對錶、列是否存在,版本號,校驗SQL等等功能。
對查詢結果只有一個值得狀況進行優化,有多個值也只取第一個值。用法實例:
/**
* 使用FMDatabaseAdditions中的intForQuery函數查找數據,若是返回結果有多個數據只取第一條數據
*/
- (void)queryForIntForQuery{
int idx = [_db intForQuery:@"SELECT id FROM t_student WHERE age = ?",@(26)];
NSLog(@"%zi",idx);
}
複製代碼
-(BOOL)tableExists:(NSString*)tableName;數據庫表是否存在。
-(BOOL)columnExists:(NSString
-(FMResultSet*)getSchema;數據庫的一些概要信息。實例以下:
/**
*schema概要信息
*/
- (void)querySchema{
//查詢數據庫schema(全部表的一些信息)
//*對於表來講, tbl_name 則仍然是表名。 但sqlite_master 中不只是表記錄,還有其它的對象,好比索引,對於索引來講 name 是索引的名字,而tbl_name 是索引所屬的表名。
FMResultSet *resultSet = [_db getSchema];
while ([resultSet next]) {
NSString *type = [resultSet stringForColumn:@"type"];
NSString *name = [resultSet stringForColumn:@"name"];
NSString *tbl_name = [resultSet stringForColumn:@"tbl_name"];
int rootpage = [resultSet intForColumn:@"rootpage"];
NSString *sql = [resultSet stringForColumn:@"sql"];
NSLog(@"\n %@ \n %@ \n %@ \n %zi \n %@",type,name,tbl_name,rootpage,sql);
}
/**
* 每一張表具體的概要信息(也就是每一列的信息)
* get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER]
*/
resultSet = [_db getTableSchema:@"t_student"];
while ([resultSet next]) {
int cid = [resultSet intForColumn:@"cid"];
NSString *name = [resultSet stringForColumn:@"name"];
NSString *type = [resultSet stringForColumn:@"type"];
int notnull = [resultSet intForColumn:@"notnull"];
int pk = [resultSet intForColumn:@"pk"];
NSLog(@"\n %zi \n %@ \n %@ \n %zi \n %zi",cid,name,type,notnull,pk);
}
}
複製代碼
-(BOOL)validateSQL:(NSString
這是一個個人iOS交流羣:624212887,羣文件自行下載,無論你是小白仍是大牛熱烈歡迎進羣 ,分享面試經驗,討論技術, 你們一塊兒交流學習成長!但願幫助開發者少走彎路。——點擊:加入
若是以爲對你還有些用,就關注小編+喜歡這一篇文章。你的支持是我繼續的動力。
下篇文章預告:·FMDB源碼分析3(FMDatabase+FMDatabaseAdditions)
文章來源於網絡,若有侵權,請聯繫小編刪除。