最近做项目使用本地化的时候出现一个崩溃,一开始是偶发,后来几乎高达99%的崩溃率。好吧,抽个空研究研究吧。
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
崩溃在FMDB的这行代码上,相信很多人都遇到过。我也百度了一番,但是大多都没有解决办法。
1. 去除多线程
这个问题其实很简单。多线程引起的。说来也好笑,后面会讲为什么出现这个bug。
出现崩溃而且毫无提示,好在是有触发点。研究了一番代码,发现是在数据库操作的外面套了一个异步子线程。。。
但FMDB不能在多个线程中共同一个FMDatabase对象,因为这个类本身不是线程安全的,如果这样使用会造成数据混乱等问题。然后巧的是之前的程序员使用的加锁是数据库本身。。。那么,我使用多线程,开辟出来的线程会持有一个数据库,主线程本身也持有该数据库,这就造成了资源竞争了。然后数据库本身可能已经不唯一了。然后你用数据库作为依据去加锁,可能已经失去了本身的意义。
所以做法很简单,去掉外部的多线程就解决问题了。
这里提一下,数据库不是不支持多线程操作,最新版的FMDB是支持多线程操作的。多线程操作同一个数据看的时候,每个线程调用数据库的时候都及时开关,其他线程要操作数据库必须等数据库关闭后才能打开操作。当然,这样会造成混乱。不怎么推荐。
FMDatabaseQueue:类似于线程队列。
使用的时候可以先判断该队列是否存在,不存在再创建:(没有MarkDown排版好难受)
FMDatabaseQueue *dbQueue = [_dbQueueDic objectForKey:dbPath];
if (dbQueue == nil) {
dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
}
//调用方式 多次调用亦串行
[queue inDatabase:^(FMDatabase *db) {
[db open];
//可以执行多条语句,串行执行
resultFlag = [db executeUpdate:sql];//普通执行
resultFlag = [db executeUpdate:sql];//普通执行
resultFlag = [db executeUpdate:sql withParameterDictionary:args];//添加参数执行
[db close];
}]
3. 关于事务
有时候我们的数据库业务,可能会考虑到数据安全唯一性,如果某段sql执行了其中一部分或者一半失效了,那么需要全部回滚,就可以用到事务操作。代码很简单,封装好的一个方法:
[self.queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
resultFlag = [db executeUpdate:sql1];
resultFlag = [db executeUpdate:sql2];
resultFlag = [db executeUpdate:sql3];
}];
3. 为什么添加多线程
这个还是线程问题,慢慢意识到线程真的很重要。问题:tableview上拉加载以后不展示无数据footer(阻止用户继续上拉加载),联网无问题,本地化有问题。那么很快定位到是因为网络请求是异步的,那么我们可以在子线程请求完成以后通过回调,拿到数据刷新UI。 但是本地化的时候就变成了全在主线程进行,然后就在本地化操作的最外面加了个异步的子线程。这样做到了异步回调的形式。先执行footerEndRefreshing后执行endRefreshingWithNoMore解决了该问题。