I'm using Swift 4, iOS 11
I'm clearly trying to do something beyond my knowledge level here, but I'm hoping someone can help.
I am implementing a table view search that fetches results from CoreData. The search became too slow, so I am now trying to run it asynchronously to avoid UI lag.
On my main VC (table view), I have the following logic that runs on textDidChange:
DispatchQueue.global(qos: .background).async {
DataManager.searchAllTables(searchText: searchText){ (returnResults) -> () in
DispatchQueue.main.async {
// LOGIC TO GIVE RETURNED RESULTS TO TABLE
self.resultsTable.reloadData()
}
}
}
Within my DataManager in the function searchAllTables, I have the following logic run on each CoreData relationship:
// Get Disease
if let disease = bug.related_disease?.allObjects {
if !disease.isEmpty {
if disease.contains(where: { (($0 as AnyObject).name?.lowercased().contains(searchTerm.lowercased()))! || (($0 as AnyObject).name?.lowercased().contains(self.convertAcronyms(searchTerm: searchTerm)))! }){
// LOGIC TO DETERMINE POINTS
}
}
}
It was working well before trying to make it async, but now it gives me the error
Thread 5: EXC_BAD_ACCESS (code=1, address=0x1a1b40cc771)
for the line of code
if let disease = bug.related_disease?.allObjects {
This error does NOT appear if the user types slowly. This makes me think that the background threads are crossing and something is happening with the object I'm trying to get from CoreData.
I know that EXC_BAD_ACCESS
means that I'm "sending a message to an object that has already been released". Can anyone help me understand how to solve this problem?
UPDATE: Added searchAllTables below
static func searchAllTables(searchText:String, completion: @escaping ((_ returnResults:[BugElement])->())) {
var bugElements:[BugElement] = []
var bugElementShell:[BugElement] = []
var returnValueRank:Int = 0
var categoryRestriction:String = "all" // Search all categories by default
var numSearchTerms:Int = searchText.components(separatedBy: " ").count
let searchTerm:String = self.getSearchPhrases(searchTerms: searchText.components(separatedBy: " ")).1
numSearchTerms = self.getSearchPhrases(searchTerms: searchText.components(separatedBy: " ")).0
// Set category restriciton if needed
if self.bugFilter.category_restricted[searchTerm.lowercased()] != nil{
categoryRestriction = self.bugFilter.category_restricted[searchTerm.lowercased()]!
}
let fetchRequest: NSFetchRequest<Bugs> = Bugs.fetchRequest()
// DOES THIS HELP SPEED?
if searchTerm.count <= 3{
let predicate = NSPredicate(format:"name beginswith[c] %@ OR ANY related_disease.name contains[c] %@ OR ANY related_general.name beginswith[c] %@ OR ANY related_gramstain.name beginswith[c] %@ OR ANY related_keypoints.name beginswith[c] %@ OR ANY related_laboratory.name beginswith[c] %@ OR ANY related_morphology.name beginswith[c] %@ OR ANY related_prevention.name beginswith[c] %@ OR ANY related_signs.name beginswith[c] %@ OR ANY related_treatments.name beginswith[c] %@ OR ANY related_type.name beginswith[c] %@", argumentArray: [searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased()])
fetchRequest.predicate = predicate
} else{
let predicate = NSPredicate(format:"name contains[c] %@ OR related_disease.name contains[c] %@ OR related_general.name contains[c] %@ OR related_gramstain.name contains[c] %@ OR related_keypoints.name contains[c] %@ OR related_laboratory.name contains[c] %@ OR related_morphology.name contains[c] %@ OR related_prevention.name contains[c] %@ OR related_signs.name contains[c] %@ OR related_treatments.name contains[c] %@ OR related_type.name contains[c] %@", argumentArray: [searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased(),searchTerm.lowercased()])
fetchRequest.predicate = predicate
}
do {
let diseases = try DataManager.context.fetch(fetchRequest)
for bug in diseases{
// Points system (all matches past array are worth 1)
var matchValue_name:[Int] = [10, 8, 4]
var matchValue_disease:[Int] = [8, 4, 3]
var matchValue_general:[Int] = [5, 3, 2]
var matchValue_gramstain:[Int] = [5, 3, 2]
var matchValue_keypoints:[Int] = [5, 3, 2]
var matchValue_laboratory:[Int] = [2]
var matchValue_morphology:[Int] = [5, 3, 2]
var matchValue_prevention:[Int] = [2]
var matchValue_signs:[Int] = [1]
var matchValue_treatment:[Int] = [5, 3, 2]
var matchValue_type:[Int] = [1]
// Break down by word
var matchedNameTerms:Int = 0
var matchedDiseaseTerms:Int = 0
var matchedGeneralTerms:Int = 0
var matchedGramStainTerms:Int = 0
var matchedKeyPointsTerms:Int = 0
var matchedLaboratoryTerms:Int = 0
var matchedMorphologyTerms:Int = 0
var matchedPreventionTerms:Int = 0
var matchedSignsTerms:Int = 0
var matchedTreatmentTerms:Int = 0
var matchedTypeTerms:Int = 0
// BEGIN: By term
var matchBasis = Set<String>()
if categoryRestriction == "all" || categoryRestriction == "name"{
// FAULT
if bug.name.lowercased().contains(searchTerm.lowercased()){
matchedNameTerms += 1
// Matched based on disease name
if matchedNameTerms > (matchValue_name.count-1){
// Match beyond point assignment
returnValueRank += 1
} else {
// Matched within point assignment, add those points
returnValueRank += matchValue_name[(matchedNameTerms-1)]
}
// Append name if first match
if matchedNameTerms == 1{
matchBasis.insert("Name")
}
}
}
// Get Disease
if categoryRestriction == "all" || categoryRestriction == "disease"{
// FAULT
if let disease = bug.related_disease?.allObjects {
if !disease.isEmpty {
if disease.contains(where: { (($0 as AnyObject).name?.lowercased().contains(searchTerm.lowercased()))! || (($0 as AnyObject).name?.lowercased().contains(self.convertAcronyms(searchTerm: searchTerm)))! }){
matchedDiseaseTerms += 1
// Matched based on Disease
if matchedDiseaseTerms > (matchValue_disease.count-1){
// Match beyond point assignment
returnValueRank += 1
} else {
// Matched within point assignment, add those points
returnValueRank += matchValue_disease[(matchedDiseaseTerms-1)]
}
// Append Disease if first match
if matchedDiseaseTerms == 1{
matchBasis.insert("Disease")
}
}
}
}
}
// ADDITIONAL MATCHES JUST LIKE ABOVE DISEASE BLOCK
// END: By term
if (matchedNameTerms + matchedDiseaseTerms + matchedGeneralTerms + matchedGramStainTerms + matchedKeyPointsTerms + matchedLaboratoryTerms + matchedMorphologyTerms + matchedPreventionTerms + matchedSignsTerms + matchedTreatmentTerms + matchedTypeTerms) > 0{
// Create Element
let bugElement = BugElement(rank: returnValueRank, name: bug.name, match: matchBasis) // Initialize struct
bugElementShell.append(bugElement)
}
returnValueRank = 0
}
} catch {
if DataManager.debug{ print("Could not get Bugs!") }
}
// Handle stored search
if numSearchTerms == 0{
// No stored search
//print("None")
self.storedBugElements.append(bugElementShell)
} else if numSearchTerms > self.storedBugElements.count{
// New search term
//print("New")
self.storedBugElements.append(bugElementShell)
} else if numSearchTerms < self.storedBugElements.count{
// Deleted search term
//print("Delete")
self.storedBugElements.removeLast()
} else if numSearchTerms == self.storedBugElements.count{
// Still typing search term
//print("Still...")
self.storedBugElements.removeLast()
self.storedBugElements.append(bugElementShell)
}
// Handle stored search
if self.storedBugElements.count > 0 {
let namedElements = self.storedBugElements.joined().map { ($0.name, $0) }
// Now combine them as you describe. Add the ranks, and merge the items
let uniqueElements =
Dictionary<String, BugElement>(namedElements,
uniquingKeysWith: { (lhs, rhs) -> BugElement in
let sum = lhs.rank + rhs.rank
return BugElement(rank: sum,
name: lhs.name,
match: lhs.match.union(rhs.match))
})
// The result is the values of the dictionary
let result = uniqueElements.values
bugElements = result.sorted { $0.rank > $1.rank }
} else{
bugElements = bugElementShell.sorted { $0.rank > $1.rank }
}
completion(bugElements)
}