一、前言
FMDB是IOS平臺的SQLite資料庫框架,以OC的方式封裝了SQLite的C語言的API。FMDB使用起來更加的物件導向,省去了很多麻煩、冗餘的C語言程式碼具體對比詳見我的簡書iOS開發資料儲存篇—libsqlite3和FMDB的基本使用和區別,對比蘋果自帶的Core Data框架,更加的輕量級和靈活。提供了多執行緒安全的資料庫操作的方法,有效的防止資料混亂。開源地址為github.com/ccgus/fmdb。
這是一個我的iOS交流群:624212887,群檔案自行下載,不管你是小白還是大牛熱烈歡迎進群 ,分享面試經驗,討論技術, 大家一起交流學習成長!希望幫助開發者少走彎路。——點選:加入
二、原始碼分析
FMDB原始碼主要有以下幾個檔案組成:
FMResultSet : 表示FMDatabase執行查詢之後的結果集。
FMDatabase : 表示一個單獨的SQLite資料庫操作例項,通過它可以對資料庫進行增刪改查等等操作。
FMDatabaseAdditions : 擴充套件FMDatabase類,新增對查詢結果只返回單個值的方法進行簡化,對錶、列是否存在,版本號,校驗SQL等等功能。
FMDatabaseQueue : 使用序列佇列 ,對多執行緒的操作進行了支援。
FMDatabasePool : 使用任務池的形式,對多執行緒的操作提供支援。(不過官方對這種方式並不推薦使用,優先選擇FMDatabaseQueue的方式:ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD)
下面我們就來逐個分析FMDB原始碼的實現方式,先講FMResultSet的實現思路。
2.1:FMResultSet
2.1.1:初始化物件
- 引數1:(FMStatement *)statement
該物件主要是對sqlite3_stmt的封裝,sqlite3_stmt * 所表示的內容可以看成是預處理過得sql語句,已經不是我們熟知的sql語句。他是一個已經把sql語句解析了,用sqlite自己表示記錄的內部資料結構。
- 引數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];
return FMDBReturnAutoreleased(rs);
}
複製程式碼
2.1.2:遍歷取得所有的結果集合
-(BOOL)next;其實是對-(BOOL)nextWithError:(NSError **)outErr;函式的封裝。主要作用是通過sqlite3_step函式對FMStatement中的sqlite3_stmt物件進行逐行取值。
/**
* 遍歷每一行的資料(fmdb:next() --》c:sqlite3_step() )
*
* @param outErr 錯誤資訊
*
* @return
*/
- (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.1.3:列名與該列的列數的一一對應關係
- @property (readonly) NSMutableDictionary *columnNameToIndexMap;物件中維護了列名與索引一一對應的關係的對照表。
/**
* 列的名稱與索引的一一對應關係
*
* @return
* @{ @“id”:@0,
* @"name":@1,
* @"age":@2
* }
*/
- (NSMutableDictionary *)columnNameToIndexMap {
if (!_columnNameToIndexMap) {
int columnCount = sqlite3_column_count([_statement statement]);
_columnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)columnCount];
int columnIdx = 0;
for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
[_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx]
forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]];
}
}
return _columnNameToIndexMap;
}
複製程式碼
-(int)columnIndexForName:(NSString*)columnName; 根據列名獲取該列的所在第幾列(列的索引)
-(NSString*)columnNameForIndex:(int)columnIdx;根據列的索引獲取該列的名稱。
2.1.4:獲得每一行中每一個列欄位的值。
- -XXXForColumnIndex:(int)columnIdx;根據列的索引獲取該列的值。
- -XXXForColumn:(NSString*)columnName;根據列的名稱獲取該列的值。
-XXXForColumnIndex:(int)columnIdx;其實是對sqlite3_column_*函式的封裝。如下所示。
- (int)intForColumnIndex:(int)columnIdx {
return sqlite3_column_int([_statement statement], columnIdx);
}
複製程式碼
由2.1.3中columnNameToIndexMap我們可以得到列名與索引的一一物件關係,那麼-XXXForColumn:(NSString*)columnName;的實現就很簡單了。
/**
* 根據列的名稱獲取int值
*
* @param columnName
*
* @return
*/
- (int)intForColumn:(NSString*)columnName {
return [self intForColumnIndex:[self columnIndexForName:columnName]];
}
複製程式碼
2.1.5:獲取每一行中所有的結果集合
/**
* 每一行資料的結果所對應的Dictionary
*
* @return
* @{
* age = 29;
* id = 1;
* name = "yixiang-20";
* }
*/
- (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.1.6:對KVC的支援
FMDB這裡的支援還是比較簡單的,只能對於String型別的屬性進行支援。
/**
* 使用KVC,把資料庫中的每一行資料對應到每一個物件,物件的屬性要和資料庫的列名保持一直。
*
* @param object 物件
*/
- (void)kvcMagic:(id)object {
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)]];
}
}
}
複製程式碼
三、最後說一點
這是一個我的iOS交流群:624212887,群檔案自行下載,不管你是小白還是大牛熱烈歡迎進群 ,分享面試經驗,討論技術, 大家一起交流學習成長!希望幫助開發者少走彎路。——點選:加入
如果覺得對你還有些用,就關注小編+喜歡這一篇文章。你的支援是我繼續的動力。
下篇文章預告:·FMDB原始碼分析2(FMDatabase+FMDatabaseAdditions)
文章來源於網路,如有侵權,請聯絡小編刪除。