博客> iOS开发源码阅读篇--FMDB源码分析3(FMDatabaseQueue+FMDatabasePool)
iOS开发源码阅读篇--FMDB源码分析3(FMDatabaseQueue+FMDatabasePool)
2019-07-21 08:37 评论:0 阅读:526 月华七子
ios FMDB 源码

一、前言

如上一章所讲,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比较优秀的地方就在于对多线程的处理。所以这一篇主要是研究FMDB的多线程处理的实现。而FMDB最新的版本中主要是通过使用FMDatabaseQueue这个类来进行多线程处理的。

这是一个我的iOS交流群:624212887,群文件自行下载,不管你是小白还是大牛热烈欢迎进群 ,分享面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。——点击:加入

二、FMDatabaseQueue源码分析

我们先来看看FMDatabaseQueue如何使用。

/**
 *  FMDatabaseQueue使用案例
 */
- (void)FMDatabaseQueueTest{
    //1、获取数据库文件路径
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"];

    //使用
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:fileName];
    [queue inDatabase:^(FMDatabase *db) {
        [db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_student_2 (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"];
        [db executeUpdate:@"INSERT INTO t_student_2 (name, age) VALUES ('yixiangZZ', 20);"];
        [db executeUpdate:@"INSERT INTO t_student_2 (name, age) VALUES ('yixiangXX', 25);"];

        FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student_2"];

        NSLog(@"%@",[NSThread currentThread]);
        while ([rs next]) {
            int ID = [rs intForColumn:@"id"];
            NSString *name = [rs stringForColumn:@"name"];
            int age = [rs intForColumn:@"age"];
            NSLog(@"%d %@ %d",ID,name,age);
        }

    }];

    //支持事务
    [queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        [db executeUpdate:@"UPDATE t_student_2 SET age = 40 WHERE name = 'yixiangZZ'"];
        [db executeUpdate:@"UPDATE t_student_2 SET age = 45 WHERE name = 'yixiangXX'"];

        BOOL hasProblem = NO;
        if (hasProblem) {
            *rollback = YES;//回滚
            return;
        }

        FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student_2"];
        NSLog(@"%@",[NSThread currentThread]);
        while ([rs next]) {
            int ID = [rs intForColumn:@"id"];
            NSString *name = [rs stringForColumn:@"name"];
            int age = [rs intForColumn:@"age"];
            NSLog(@"%d %@ %d",ID,name,age);
        }

    }];
}

FMDB的多线程支持实现主要是依赖于FMDatabaseQueue这个类。下面我们来看看他是如何实现的。

2.1:初始化Queue。生成一个串行队列。

+ (instancetype)databaseQueueWithPath:(NSString*)aPath {
    FMDatabaseQueue *q = [[self alloc] initWithPath:aPath];
    FMDBAutorelease(q);
    return q;
}
- (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);
      //给当前queue生成一个标示,给_queue这个GCD队列指定了一个kDispatchQueueSpecificKey字符串,并和self(即当前FMDatabaseQueue对象)进行绑定。日后可以通过此字符串获取到绑定的对象(此处就是self)。
        dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
        _openFlags = openFlags;
    }
    return self;
}

2.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);
}

之于为什么要用dispatch_queue_set_specific和dispatch_get_specific判断是不是当前queue,是因为为了防止多线程操作时候出现死锁。可以参考告诉你告诉你dispatch_queue_set_specific和dispatch_get_specific是个什么鬼 是个什么鬼和被废弃的dispatch_get_current_queue

我们可以看出,一个queue就是一个串行队列。就算你开启多线程执行,它依然还是串行执行的。保证的线程的安全性。看下面一个案例:

/**
 *  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=11; i=20; xss=removed xss=removed xss=removed i=0; i=5; xss=removed i=0; i=5; xss=removed>= 3007000
    static unsigned long savePointIdx = 0;
    __block NSError *err = 0x00;
    FMDBRetain(self);
    dispatch_sync(_queue, ^() { 

        NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++];

        BOOL shouldRollback = NO;

        if ([[self database] startSavePointWithName:name error:&err]) {//设置一个存档点

            block([self database], &shouldRollback);

            if (shouldRollback) {
                // We need to rollback and release this savepoint to remove it
                [[self database] rollbackToSavePointWithName:name error:&err];//回滚到存档点
            }
            [[self database] releaseSavePointWithName:name error:&err];//释放该存档

        }
    });
    FMDBRelease(self);
    return err;
#else
    NSString *errorMessage = NSLocalizedString(@"Save point functions require SQLite 3.7", nil);
    if (self.logsErrors) NSLog(@"%@", errorMessage);
    return [NSError errorWithDomain:@"FMDatabase" code:0 userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
#endif
}

三、FMDatabasePool

FMDatabasePool : 使用任务池的形式,对多线程的操作提供支持。

不过官方对这种方式并不推荐使用(ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD),优先选择FMDatabaseQueue的方式。

平时基本也不使用,官方也不推荐使用。这里就不多讲了。

四、参考资料

FMDB源码

FMDB源码阅读系列

五、最后说一点

这是一个我的iOS交流群:624212887,群文件自行下载,不管你是小白还是大牛热烈欢迎进群 ,分享面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。——点击:加入

如果觉得对你还有些用,就关注小编+喜欢这一篇文章。你的支持是我继续的动力。

下篇文章预告:ObjectC Hook函数的实现与实战

文章来源于网络,如有侵权,请联系小编删除。

收藏
0
sina weixin mail 回到顶部