您的位置:首页 > 数据库

IOS 数据库(FMDB)--- 源码介绍(FMDBDataBase)

2017-12-26 20:20 453 查看

基本操作

NSString* docsdir = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 将user.sqlite放到Documents文件夹下,并生成user.sqlite的绝对路径
NSString* dbpath = [docsdir stringByAppendingPathComponent:@"user.sqlite"];
// 根据user.sqlite的绝对路径获取到一个FMDatabase对象,其实就是一个封装了的SQLite数据库对象
FMDatabase* db = [FMDatabase databaseWithPath:dbpath];
// 打开该数据库
[db open];
// 执行SQL语句 - select * from people
FMResultSet *rs = [db
executeQuery
:@"select * from people"];
// 利用next函数,循环输出结果
while ([rs next]) {
NSLog(@"%@ %@",
[rs stringForColumn:@"firstname"],
[rs stringForColumn:@"lastname"]);
}
// 关闭该数据库
[db close];


很简单是吧,甚至我觉得上面我写的注释都多余了。确实,FMDB说白了就是对SQLite数据库的C/C++接口进行了一层封装,当然功能也更为强大,比如多线程操作,另外FMDB接口要比原生的SQLite接口简洁很多。下面我们就上面的例子研究下FMDB的基本流程。

FMDBDataBase

1.[FMDatabase databaseWithPath:]---初始化工作

#pragma mark FMDatabase instantiation and deallocation

+ (instancetype)databaseWithPath:(NSString*)aPath {
return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
}

- (instancetype)init {
return [self initWithPath:nil];
}

- (instancetype)initWithPath:(NSString*)aPath {

assert(sqlite3_threadsafe()); // whoa there big boy- gotta make sure sqlite it happy with what we're going to do.

self = [super init];

if (self) {
_databasePath               = [aPath copy];
_openResultSets             = [[NSMutableSet alloc] init];
_db                         = nil;
_logsErrors                 = YES;
_crashOnErrors              = NO;
_maxBusyRetryTimeInterval   = 2;
}

return self;
}


2.[FMDatabase open]---打开数据库

sqlite3_open(const char *filename, sqlite3 **ppDb)

该例程打开一个指向 SQLite 数据库文件的连接,返回一个用于其他 SQLite 程序的数据库连接对象。

如果 filename 参数是 NULL 或 ':memory:',那么 sqlite3_open() 将会在 RAM 中创建一个内存数据库,这只会在 session 的有效时间内持续。

如果文件名 filename 不为 NULL,那么 sqlite3_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;
}

if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
}

return YES;
}


- (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout {

_maxBusyRetryTimeInterval = timeout;

if (!_db) {
return;
}
// 处理的handler设置为FMDBDatabaseBusyHandler这个函数
if (timeout > 0) {
sqlite3_busy_handler(_db, &FMDBDatabaseBusyHandler, (__bridge void *)(self));
}
else {
// 不使用任何busy handler处理
sqlite3_busy_handler(_db, nil, nil);
}
}


// 注意:appledoc(生成文档的软件)中,对于有具体实现的C函数,比如下面这个函数,
// 是有bug的。所以你在生成文档时,忽略.m文件。
// 该函数就是简单调用sqlite3_sleep来挂起进程
static int FMDBDatabaseBusyHandler(void *f, int count) {
FMDatabase *self = (__bridge FMDatabase*)f;
// 如果count为0,表示的第一次执行回调函数
// 初始化self->_startBusyRetryTime,供后面计算delta使用
if (count == 0) {
self->_startBusyRetryTime = [NSDate timeIntervalSinceReferenceDate];
return 1;
}
// 使用delta变量控制执行回调函数的次数,每次挂起50~100ms
// 所以maxBusyRetryTimeInterval的作用就在这体现出来了
// 当挂起的时长大于maxBusyRetryTimeInterval,就返回0,并停止执行该回调函数了
NSTimeInterval delta = [NSDate timeIntervalSinceReferenceDate] - (self->_startBusyRetryTime);

if (delta < [self maxBusyRetryTimeInterval]) {
// 使用sqlite3_sleep每次当前线程挂起50~100ms
int requestedSleepInMillseconds = (int) arc4random_uniform(50) + 50;
int actualSleepInMilliseconds = sqlite3_sleep(requestedSleepInMillseconds);
// 如果实际挂起的时长与想要挂起的时长不一致,可能是因为构建SQLite时没将HAVE_USLEEP置为1
if (actualSleepInMilliseconds != requestedSleepInMillseconds) {
NSLog(@"WARNING: Requested sleep of %i milliseconds, but SQLite returned %i. Maybe SQLite wasn't built with HAVE_USLEEP=1?", requestedSleepInMillseconds, actualSleepInMilliseconds);
}
return 1;
}

return 0;
}


2.[FMDatabase executeQuery]---有结果查询

- (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) {
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);

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
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 {

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) {
statement = [[FMStatement alloc] init];
[statement setStatement:pStmt];

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;
}



3.[FMDatabase executeQuery]---无结果查询

- (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 (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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: