I'm trying to get my head around GCD, specifically DispatchGroup to organise downloads to a SQLite database via the FMDB wrapper. My app does the following:
wait(timeout:)
function in the future.However, with my implementation of DispatchGroup (below) I'm receiving the following errors.
API call with NULL database connection pointer
[logging] misuse at line 125820 of [378230ae7f]
And also
BUG IN CLIENT OF libsqlite3.dylib: illegal multi-threaded access to database connection
Code as follows:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Download from server
if availableSubjects[indexPath.row].isDownloaded == 0 {
//CHAINING THIS WAY WORKS
/* downloadModel.downloadCaseBundle(withSubjectID: indexPath.row, completion: {
self.downloadModel.downloadToken(forSubject: indexPath.row, completion: {
self.caseBundle = DBManager.sharedDBManager.getCaseBundle(forSubject: indexPath.row)
self.availableSubjects[indexPath.row].isDownloaded = 1
DispatchQueue.main.async {
self.performSegue(withIdentifier: "showCaseList", sender: self)
}
})
})*/
let dispatchGroup = DispatchGroup()
//Download content
dispatchGroup.enter()
downloadModel.downloadCaseBundle(withSubjectID: indexPath.row) {
dispatchGroup.leave()
}
//Download token
dispatchGroup.enter()
downloadModel.downloadToken(forSubject: indexPath.row) {
dispatchGroup.leave()
}
//Execute
dispatchGroup.notify(queue: .main) {
self.caseBundle = DBManager.sharedDBManager.getCaseBundle(forSubject: indexPath.row)
self.availableSubjects[indexPath.row].isDownloaded = 1
self.performSegue(withIdentifier: "showCaseList", sender: self)
}
} else { //Already downloaded, just retrieve from local db and present
caseBundle = DBManager.sharedDBManager.getCaseBundle(forSubject: indexPath.row)
self.performSegue(withIdentifier: "showCaseList", sender: self)
}
}
downloadToken function is more or less identical
func downloadCaseBundle(withSubjectID subjectID: Int, completion: @escaping () -> Void) {
let urlPath = "someStringtoRemoteDB"
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error")
} else {
print("cases downloaded")
self.parseCasesJSON(data!, header: self.remoteMasterTable, forSubject: subjectID)
completion()
}
}
task.resume()
}
func parseCasesJSON(_ data:Data, header: String, forSubject subjectID: Int) {
var jsonResult = NSArray()
var jsonElement = NSDictionary()
let cases = NSMutableArray()
do {
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
print("error at serialisation")
}
//Iterate through JSON result (i.e. case), construct and append to cases array
for i in 0 ..< jsonResult.count {
jsonElement = jsonResult[i] as! NSDictionary
var caseObject = CaseModel()
//The following insures none of the JsonElement values are nil through optional binding
if let uniqueID = jsonElement["id"] as? Int,
let subjectTitle = jsonElement["subjectTitle"] as? String,
let subjectID = jsonElement["subjectID"] as? Int,
let questionID = jsonElement["questionID"] as? Int,
//And so on
{
caseObject.uniqueID = uniqueID
caseObject.subjectTitle = subjectTitle
caseObject.subjectID = subjectID
caseObject.questionID = questionID
//And so on
}
cases.add(caseObject)
}
DBManager.sharedDBManager.saveCasesLocally(dataToSave: cases as! [CaseModel])
DBManager.sharedDBManager.setSubjectAsDownloaded(forSubjectID: subjectID)
}
Turns out it was nothing to do with those methods and I needed to implement FMDatabaseQueue
instead of FMDatabase
in my DBManager
singleton.