BUG現(xiàn)象
報錯 The FMDatabase is currently in use
報錯 Closing leaked statement
寫在前面
之前寫DEMO的時候其實存儲數(shù)據(jù)用到的NSKeyedArchiver序列化和反序列化比較多探熔。代碼簡單抖棘,存儲到沙盒也比較安全。只是存儲的模型數(shù)據(jù)需要實現(xiàn)NScoding協(xié)議巢钓。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSKeyedArchiver archiveRootObject:_dataSource toFile:MLBCacheHomeItemFilePath];
});
沙盒的安全性包裝第三方軟件不能拿到你的app沙盒中的數(shù)據(jù)错忱,這也是為什么不要金山軟件等清理垃圾數(shù)據(jù)的原因霍弹,它只是做了一個假動畫谒撼,創(chuàng)造了相同數(shù)量的垃圾,然后再清除自己熄捍。
但之前有一個即時聊天的APP烛恤,會有大量的存數(shù)據(jù),查消息的操作余耽,性能需要數(shù)據(jù)庫實現(xiàn)缚柏。
一開始我看著網(wǎng)上的一些例子基本是這么寫的
#pragma mark - 接口
- (void)addPerson:(Person *)person{
[_db open];
NSNumber *maxID = @(0);
FMResultSet *res = [_db executeQuery:@"SELECT * FROM person "];
//獲取數(shù)據(jù)庫中最大的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];
}
確實在一開始的使用情況下,沒有出現(xiàn)什么問題宾添。
可是在我們的APP開始大量數(shù)據(jù)暴力測試的時候船惨,高量的多線程快速寫入查詢下柜裸。出現(xiàn)了閃退,報錯信息如下:
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 <FMDatabase: 0x170082f30> is currently in use.
2017-03-01 16:26:42.012397 traffic.fm[2606:1262649] The FMDatabase <FMDatabase: 0x170082f30> is currently in use.
2017-03-01 16:26:42.012478 traffic.fm[2606:1262649] Closing leaked statement
原因是在多線程下粱锐,高并發(fā)下不同線程對數(shù)據(jù)的競爭疙挺,對一個數(shù)據(jù)進行重復(fù)寫入修改。
需要使用隊列實現(xiàn)才能避免這樣的問題
正確的姿勢 請?zhí)珊?/h2>
// Created by frank on 16/11/23.
// Copyright ? 2016年 TopHeavier. All rights reserved.
//
#import "MessageDatabase.h"
#import <FMDB.h>
#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;
}
// 懶加載數(shù)據(jù)庫隊列
- (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(@"創(chuàng)建表格成功");
} else {
NSLog(@"創(chuàng)建表格失敗");
}
}];
}
#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(@"插入數(shù)據(jù)成功");
} else {
NSLog(@"插入數(shù)據(jù)失敗");
}
}];
}
-(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(@"查詢最近數(shù)據(jù)失敗");
}
}];
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(@"刪除數(shù)據(jù)成功");
} else {
NSLog(@"刪除數(shù)據(jù)失敗");
}
}];
}
- (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
這個例子告訴我們有時候可能你覺得自己很水怜浅,但網(wǎng)上比你水的人更多铐然。
// Created by frank on 16/11/23.
// Copyright ? 2016年 TopHeavier. All rights reserved.
//
#import "MessageDatabase.h"
#import <FMDB.h>
#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;
}
// 懶加載數(shù)據(jù)庫隊列
- (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(@"創(chuàng)建表格成功");
} else {
NSLog(@"創(chuàng)建表格失敗");
}
}];
}
#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(@"插入數(shù)據(jù)成功");
} else {
NSLog(@"插入數(shù)據(jù)失敗");
}
}];
}
-(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(@"查詢最近數(shù)據(jù)失敗");
}
}];
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(@"刪除數(shù)據(jù)成功");
} else {
NSLog(@"刪除數(shù)據(jù)失敗");
}
}];
}
- (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
用新東西的時候一定要了解其底層實現(xiàn)原理,要參考就參考官方庫的例子恶座。