zoukankan      html  css  js  c++  java
  • IOS 数据库(FMDB)--- 源码介绍(FMDBDataBase)

    基本操作

    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);
    }
  • 相关阅读:
    Gof 设计模式
    系统架构师-笔记
    微信小程序弹出层点击穿透问题
    微信小程序-两个input叠加,多次点击字体变粗或闪动
    微信小程序-下拉松开弹不回去顶部留一段空白
    node+vue报错合辑
    Nodejs脚手架搭建基于express的应用
    Vue 核心之数据劫持
    Date——时间戳转化为YYYY-MM-DD h:m:s时间格式
    web页面加载、解析、渲染过程
  • 原文地址:https://www.cnblogs.com/guchengfengyun/p/8119837.html
Copyright © 2011-2022 走看看