最近做一个二手项目,首页使用FMDB初始化了很多数据,之后应为需求需要,又加了很多初始化数据.
FMDB单例的写法是这样的:
+ (IMCache *)shareIMCache
{
static IMCache *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[IMCache alloc] init];
});
return instance;
}
- (instancetype)init
{
self = [super init];
if (self) {
NSString * dbPath = [PATH_USER_DOCUMENT stringByAppendingPathComponent:@"imcache.db"];
dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
[dbQueue inDatabase:^(FMDatabase *db) {
[db executeUpdate:[IMCache createMessageTableSql]];
[db executeUpdate:[IMCache createContectTableSql]];
}];
}
return self;
}
把数据库封装成了一个单例,添加增删改查等常用方法
使用FMDBQueue来避免访问数据库竞争冲突
FMDatabaseQueue解决这个问题的思路是:创建一个队列,然后将放入队列的block顺序执行,这样避免了多线程同时访问数据库
然而FMDB还是不可避免的就蹦了
报的就是databaseislocked
然后就开始梳理多线程的冲突,尽量避免同一时间去初始化封装成的这个单例
然而还是偶尔会崩溃
然后还是从头开始在彻查了FMDB本身
FMDatabase不能多线程使用同一个实例
前面提过了,FMdatabase是无法多线程同时访问同一个FMDatabase实例的,否则会引起崩溃.
官方给出的方案就是使用FMDatabaseQueue来解决这个问题,然而FMDatabaseQueue
能够解决的问题仅仅在于多个数据库访问请求的冲突,并不能解决多线程中多次同时初始化FMDatabase引起的崩溃
使用GCD解决多线程冲突
在知道了这问题后,想到的解决方案就是:
将不同线程多个初始化请求放入同一个队列排队依次执行
具体步骤为:
1.在单例初始化方法里初始化一个全局的串行队列
- (instancetype)init
{
self = [super init];
if (self) {
NSString * dbPath = [PATH_USER_DOCUMENT stringByAppendingPathComponent:@"imcache.db"];
dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
[dbQueue inDatabase:^(FMDatabase *db) {
[db executeUpdate:[IMCache createMessageTableSql]];
[db executeUpdate:[IMCache createContectTableSql]];
}];
serialQueue =dispatch_queue_create("FMDBThread", DISPATCH_QUEUE_SERIAL);
}
return self;
}
2.在每一个访问DataBase的方法中调用dispatch_barrier_async()方法,使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。这样,就能够顺利解决在inDatabase后还没有关闭就再次inDatabase。
/**
* 删除所有联系人的所有聊天记录
*/
- (void)deleteAllMessages:(void(^)(BOOL success))completion
{
dispatch_barrier_async(serialQueue, ^(){
[dbQueue inDatabase:^(FMDatabase *db) {
// 查找出所有群的默认消息
NSString * sql = [NSString stringWithFormat:@"SELECT * FROM messages WHERE savetime=%@", @"0"];
NSMutableArray * messages = [NSMutableArray array];
FMResultSet * result = [db executeQuery:sql];
while ([result next]) {
CacheMessage * message = [self valueToMessage:result];
[messages addObject:message];
}
[result close];
// 删除聊天表下所有记录
NSString * deletesql = [NSString stringWithFormat:@"delete from messages"];
BOOL delete = [db executeUpdate:deletesql];
ZYNLog(@"删除聊天表 %d", delete);
// 添加群的默认消息
for (CacheMessage * msg in messages) {
NSString * insert = [NSString stringWithFormat:@"insert into messages (fromcln,tocln,uid,type,contype,status,body,sendtime,receivetime,savetime,sessionid) values ('%@','%@','%@',%d,%d,%d,\"%@\",'%@','%@',%lld,'%@')", msg.from, msg.to, msg.uid, msg.type, msg.contype, msg.status, msg.body, msg.sendtime, msg.sendtime, msg.savetime, msg.sessionid];
[db executeUpdate:insert];
}
ZYNLog(@"添加群默认消息完成");
completion(true);
}];
});
}