iOS-BUG-The FMDatabase is currently in use and Closing leaked statement

BUG现象

报错 The FMDatabase is currently in use
报错 Closing leaked statement

写在前面

之前写DEMO的时候其实存储数据用到的NSKeyedArchiver序列化和反序列化比较多。代码简单,存储到沙盒也比较安全。只是存储的模型数据需要实现NScoding协议。

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    [NSKeyedArchiver archiveRootObject:_dataSource toFile:MLBCacheHomeItemFilePath];
                });

沙盒的安全性包装第三方软件不能拿到你的app沙盒中的数据,这也是为什么不要金山软件等清理垃圾数据的原因,它只是做了一个假动画,创造了相同数量的垃圾,然后再清除自己。

但之前有一个即时聊天的APP,会有大量的存数据,查消息的操作,性能需要数据库实现。

一开始我看着网上的一些例子基本是这么写的

#pragma mark - 接口

- (void)addPerson:(Person *)person{
    [_db open];

    NSNumber *maxID = @(0);

    FMResultSet *res = [_db executeQuery:@"SELECT * FROM person "];
    //获取数据库中最大的ID
    while ([res next]) {
        if ([maxID integerValue] < [[res stringForColumn:@"person_id"] integerValue]) {
            maxID = @([[res stringForColumn:@"person_id"] integerValue] ) ;
        }
    }
    maxID = @([maxID integerValue] + 1);

    [_db executeUpdate:@"INSERT INTO person(person_id,person_name,person_age,person_number)VALUES(?,?,?,?)",maxID,person.name,@(person.age),@(person.number)];
    [_db close];
}

确实在一开始的使用情况下,没有出现什么问题。
可是在我们的APP开始大量数据暴力测试的时候,高量的多线程快速写入查询下。出现了闪退,报错信息如下:

2017-03-01 15:12:51.987912 traffic.fm[4186:1323093] DB Query: INSERT INTO message(id,type,geo_location,geo_path,radius,expire_date,title,description,content,head_img_url,link_url,priority,pub_user_id,geo_hash,gmt_create,gmt_modified)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
2017-03-01 15:12:51.987987 traffic.fm[4186:1323093] Unknown error finalizing or resetting statement (10: disk I/O error)
2017-03-01 15:12:51.988047 traffic.fm[4186:1323093] DB Query: INSERT INTO message(id,type,geo_location,geo_path,radius,expire_date,title,description,content,head_img_url,link_url,priority,pub_user_id,geo_hash,gmt_create,gmt_modified)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)



2017-03-01 16:26:42.012233 traffic.fm[2606:1262649] The FMDatabase  is currently in use.
2017-03-01 16:26:42.012397 traffic.fm[2606:1262649] The FMDatabase  is currently in use.
2017-03-01 16:26:42.012478 traffic.fm[2606:1262649] Closing leaked statement

原因是在多线程下,高并发下不同线程对数据的竞争,对一个数据进行重复写入修改。

需要使用队列实现才能避免这样的问题

正确的姿势 请躺好

//  Created by frank on 16/11/23.
//  Copyright © 2016年 TopHeavier. All rights reserved.
//

#import "MessageDatabase.h"
#import 
#import "FMDatabaseQueue.h"


static MessageDatabase *_instance = nil;

@interface MessageDatabase()
@property (nonatomic, strong) FMDatabaseQueue *queue;
@end

@implementation MessageDatabase

+(instancetype)sharedDataBase{
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    
    return _instance;
}


// 懒加载数据库队列
- (FMDatabaseQueue *)queue {
    if (_queue == nil) {
        NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        // 文件路径
        NSString *filePath = [documentsPath stringByAppendingPathComponent:@"model.sqlite"];
        _queue = [FMDatabaseQueue databaseQueueWithPath:filePath];
    }
    return _queue;
}

-(void)initDataBase{
    // 获得Documents目录路径
    NSString * sql = @"CREATE TABLE IF NOT EXISTS 'message'  ('id' VARCHAR(64) PRIMARY KEY ,'type' int(1),'geo_location' VARCHAR(64),'geo_path' VARCHAR(300),'radius'int(11),'expire_date' timestamp,'title' VARCHAR(64),'description' VARCHAR(200),'content' VARCHAR(1200),'head_img_url' VARCHAR(300),'link_url' VARCHAR(64),'priority' int(11),'pub_user_id' varchar(64),'geo_hash' VARCHAR(64),'gmt_create' timestamp,'gmt_modified' timestamp) ";
    [self.queue inDatabase:^(FMDatabase *db) {
        BOOL result = [db executeUpdate:sql withArgumentsInArray:nil];
        if (result) {
            NSLog(@"创建表格成功");
        } else {
            NSLog(@"创建表格失败");
        }
    }];
    
  
}

#pragma mark - 接口

-(void)addMessage:(MessageObject *)MessageObject{
    [self.queue inDatabase:^(FMDatabase *db) {
        BOOL result =   [db executeUpdate:@"INSERT OR IGNORE INTO message(id,type,geo_location,geo_path,radius,expire_date,title,description,content,head_img_url,link_url,priority,pub_user_id,geo_hash,gmt_create,gmt_modified)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",MessageObject.uniqueId,MessageObject.type,MessageObject.geoLocation,MessageObject.geoPath,MessageObject.radius,MessageObject.expireDate,MessageObject.title,MessageObject.messageDescription,MessageObject.content,MessageObject.headImgUrl,MessageObject.linkUrl,MessageObject.priority,MessageObject.pubUserId,MessageObject.geoHash,MessageObject.gmtCreate,MessageObject.gmtModified];;
        if (result) {
            NSLog(@"插入数据成功");
        } else {
            NSLog(@"插入数据失败");
        }
    }];
    
    
}



-(NSMutableArray *)searchRecentMessage:(NSNumber *)num{
  
    NSMutableArray *dataArray = [[NSMutableArray alloc] init];
        [self.queue inDatabase:^(FMDatabase *db) {
        FMResultSet * res =   [db executeQuery:@"SELECT * FROM message order by gmt_create desc limit ? ",num];;
        if (res) {
            while ([res next]) {
            }
        } else {
            NSLog(@"查询最近数据失败");
        }
    }];
    
    NSEnumerator *enumerator = [dataArray reverseObjectEnumerator];
    NSMutableArray *reseverArr= [[NSMutableArray alloc]initWithArray: [enumerator allObjects]];
    return reseverArr;
    
}

-(NSMutableArray *)getMessageBefore:(NSDate *)startTime pageSize:(NSNumber *)pageSize page:(NSNumber *)page {
    NSMutableArray *dataArray = [[NSMutableArray alloc] init];
    NSInteger offsetInterValue  = pageSize.intValue-1;
    NSNumber * offset = [NSNumber numberWithInteger:offsetInterValue];

    [self.queue inDatabase:^(FMDatabase *db) {
        FMResultSet *res = [db executeQuery:@"SELECT * FROM message where gmt_create <=? order by gmt_create asc limit ? offset ?",startTime,pageSize,offset];
        if (res) {
            while ([res next]) {
            }
        } else {
            NSLog(@"查询之前消息失败");
        }
    }];

        return dataArray;
}


-(void)deleteMessage:(MessageObject *)MessageObject{
    [self.queue inDatabase:^(FMDatabase *db) {
        BOOL result =   [db executeUpdate:@"DELETE FROM message WHERE id = ?",MessageObject.uniqueId];
        if (result) {
            NSLog(@"删除数据成功");
        } else {
            NSLog(@"删除数据失败");
        }
    }];
}

- (NSMutableArray *)getAllMessage{

    NSMutableArray *dataArray = [[NSMutableArray alloc] init];
    
    [self.queue inDatabase:^(FMDatabase *db) {
        FMResultSet *res = [db executeQuery:@"SELECT * FROM message"];
        while ([res next]) {
    }
    }];
    return dataArray;

}


@end

这个例子告诉我们有时候可能你觉得自己很水,但网上比你水的人更多。

用新东西的时候一定要了解其底层实现原理,要参考就参考官方库的例子。

做技术要自信。

官方地址

你可能感兴趣的:(iOS-BUG-The FMDatabase is currently in use and Closing leaked statement)