前言
iOS 開發(fā)中,一些項(xiàng)目可能就需要存儲(chǔ)一些數(shù)據(jù),常用的方式有 NSUserDefaults 荣堰、Plist、NSFileManager 文件讀寫竭翠、NSArchiver 解歸檔振坚、CoreData 等。
在這些存儲(chǔ)方式滿足不了的時(shí)候就要用數(shù)據(jù)了斋扰,移動(dòng)端輕量級(jí)的數(shù)據(jù)庫就是 sqlite 了渡八,嵌入式的數(shù)據(jù)庫,本質(zhì)就是一個(gè)文件传货,但原生的 API 用起來又極其不方便了屎鳍,第三方框架 FMDB 成為了大多數(shù) iOS 開發(fā)者的首選。
說實(shí)話损离,現(xiàn)在接觸的項(xiàng)目可能都沒那么大哥艇,沒那么多數(shù)據(jù)要存儲(chǔ)绝编,這幾年沒怎么用僻澎,只是幾年前用了幾次,但每次總會(huì)遇到一些問題十饥,這不現(xiàn)在又遇到了窟勃,特此記錄一下,供大家參考逗堵,有不對(duì)的請(qǐng)指正:
一秉氧、FMDB 插入數(shù)據(jù)不成功
今天建完表,在插入數(shù)據(jù)的時(shí)候無論也插入不成功蜒秤,一直失敗汁咏,找不到原因亚斋。
當(dāng)你知道原因后真想抽自己一個(gè)大嘴巴子 ,不細(xì)心攘滩。
開始是這樣的帅刊,乍一看,貌似沒有什么問題漂问,
BOOL result = [db executeUpdate:@"INSERT INTO Chat_Conversation (MsgCreateTime, MsgServerTime, MesSendUserID , MsgDirection, MsgContent, MsgStatus, MsgType, ConversationType) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", locTime,serverTimeStamp, isSender?message.toUserId:message.fromUserId, isSender?@1:@0, msgContent, status, @(message.messageType), @(message.conversationType)];
但調(diào)試時(shí)會(huì)發(fā)現(xiàn)有數(shù)據(jù)為 nil, 導(dǎo)致插入失敗赖瞒。
另外一種方法:
NSString *insertSql = [NSString stringWithFormat:@"INSERT INTO Chat_Conversation (MsgCreateTime, MsgServerTime, MesSendUserID, MsgDirection, MsgContent,MsgStatus, MsgType,ConversationType) VALUES (%@, %@, %@, %@, %@, %@, %@, %@)", locTime,serverTimeStamp, sendUId, isSender?@1:@0, msgContent, status, @(message.messageType), @(message.conversationType)];
BOOL result = [db executeUpdate:insertSql];
但是你發(fā)現(xiàn),還是一直失敗蚤假,因?yàn)檫@樣用字符串拼接的話栏饮,字段為字符串的要加上單引號(hào)
就好了。
兩種方式磷仰,推薦第一種:
另外要注意的一點(diǎn)就是:
字段為 Interger 的在插入數(shù)據(jù)的時(shí)候要轉(zhuǎn)換為 NSNumber 類型的才可以袍嬉。
二、FMDB死鎖問題
inDatabase: was called reentrantly on the same queue, which would lead to a deadlock
不能在執(zhí)行一個(gè)fmdbqueue還沒有執(zhí)行完畢的時(shí)候芒划,去執(zhí)行另外一個(gè)fmdbqueue冬竟。
使用fmdb進(jìn)行數(shù)據(jù)庫操作,出現(xiàn)inDatabase: was called reentrantly on the same queue, which would lead to a deadlock這樣的崩潰錯(cuò)誤.原因是在一個(gè)[queue inDataBase]的block中,又執(zhí)行了一個(gè)inDataBase.這時(shí)第一個(gè)block需要執(zhí)行結(jié)束才能出隊(duì)列,第二個(gè)block才能執(zhí)行.但是第一個(gè)block又包含了第二個(gè)block,也就是說第二個(gè)block不執(zhí)行,第一個(gè)block就不能結(jié)束.于是就互相等待,也就是各種死鎖.
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
這個(gè)問題因?yàn)? [self.databaseQueue inDatabase:^(FMDatabase * _Nonnull db) {
}]; 中人未執(zhí)行完又執(zhí)行了新的任務(wù),例如插入數(shù)據(jù)時(shí)更新其他表數(shù)據(jù)等民逼,這時(shí)就會(huì)崩潰泵殴。
同上面的線程鎖一樣的,這是多線程訪問數(shù)據(jù)庫導(dǎo)致的 crash拼苍。
在多線程使用 FMDatabaseQueue 的確很安全笑诅,通過 GCD 的串行隊(duì)列來保證所有讀寫操作都是串行執(zhí)行的,但是分析可以看到崩潰發(fā)生在函數(shù) [FMResultSet reset]疮鲫,使用 FMDatabaseQueue 還是發(fā)生了多線程使用同一個(gè)數(shù)據(jù)庫連接吆你、預(yù)處理語句的情況,于是就崩潰了俊犯。
解決方案:
如果用 while 循環(huán)遍歷 FMResultSet 就不存在該問題妇多,因?yàn)?[FMResultSet next] 遍歷到最后會(huì)調(diào)用 [FMResultSet close]。
[_queue inDatabase:^(FMDatabase * _Nonnull db) {
FMResultSet *result = [db executeQuery:@"select * from test where a = '1'"];
// 安全
while ([result next]) {
}
// 安全
if ([result next]) {
}
[result close];
}];
如果一定要用 if ([result next]) 燕侠,手動(dòng)加上 [FMResultSet close] 也沒有問題者祖,這樣操作在多線程訪問修改時(shí)仍然會(huì)。
FMDB線程安全問題
最近在使用 FMDB 操作數(shù)據(jù)庫時(shí)绢彤,總在出現(xiàn)這個(gè)問題七问,頻繁的崩潰,報(bào)錯(cuò)信息如下:
從在控制臺(tái)能看到報(bào)錯(cuò):
[logging] BUG IN CLIENT OF sqlite3.dylib: illegal multi-threaded access to database connection
一看就知道是線程的問題茫舶,多線程訪問數(shù)據(jù)庫造成的械巡,其實(shí)原因早已經(jīng)都知道了,下面更新代碼的問題:
原因找到,重要的是要怎么改讥耗,來避免有勾,
這個(gè)問題比較棘手,嘗試了不少方法古程,但是一直都不好使柠衅。
在 [FMDatabaseQueue inDatabase:] 函數(shù)的最后,調(diào)用 [db closeOpenResultSets] 幫助調(diào)用者關(guān)閉所有 FMResultSet籍琳。