I think I need to find a way to better safely query the URI type as a string or more likely, fix a concurrency issue. For more detail...
I'm building a SwiftUI view that allows people to link up objects (basically tag an object).
With the SwiftUI view, based on the user's input the NSPredicated changes. It filters out the user's selected objects and then matches against name or url with a contains. This is where the crash is happening - in that compound predicate.
The view is the Tag
edit view and I am adding the objects to it.
private func updateNSPredicate(for query: String) -> NSPredicate? {
if !query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty{
return NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "NOT(id IN $ids)").withSubstitutionVariables(["ids": filtered]),
NSPredicate(format: "(name contains[cdl] $query) or (url contains[cdl] $query)").withSubstitutionVariables(["query": query]),
])
} else {
return defaultPredicate
}
}
NOTE: In my CloudKit database these types are:
name = String
url = URI
This always works with objects that are created already, the filter with url contains $query
has been used elsewhere for many months with no problems.
However, sometimes when I save the Tag
. It will crash.
I get the following:
Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Can't use in/contains operator with collection https://wwww.example.com (not a collection) with userInfo (null)
I can observe if I remove the or (url contains[cdl] $query)
then this crash never happens or it seems to be so rare I can't reproduce it after numerous tries. Where as when I have the url query in, it will be guaranteed to crash on the 2nd or 3rd try. Typically the more objects I add to the relationship the more likely it will happen.
Removing all objects from the tag will not cause a crash. It's only the act of creating new relationships that seems to cause it.
My concern is it's something about my persistent history tracker. While I can see how to stop the crash, I know I should be able to query url. The error seems to think it's a problem with change processing too - even though that predicate shouldn't be used in the merging of changes.
So, I'm guessing I'm hitting some sort of concurrency bug but I'm not getting where it goes wrong.
Without posting too much I hope I can show which contexts I'm using and roughly how they're being used. I fairly closely following this example if that helps: https://www.avanderlee.com/swift/persistent-history-tracking-core-data/
// set up history tracking with main container
historyTracker = PersistentHistoryTracker(persistentContainer: container, actor: storageActor, userDefaults: AppGroupUserDefaults)
// Later in the observer create a background context
let bgContext = persistentContainer.newBackgroundContext()
let history = try fetcher.fetch()
bgContext.performAndWait {
// fetch history then
history.merge(into: bgContext)
// main context
viewContext.perform {
history.merge(into: self.viewContext)
}
}
The stack does show it on the Predicate creation side but I'm assuming it's a concurrency issue given it only happens in one scenario.
0 CoreFoundation 0x000000018040e7c8 __exceptionPreprocess + 172
1 libobjc.A.dylib 0x0000000180051144 objc_exception_throw + 56
2 Foundation 0x0000000180b38cec -[NSInPredicateOperator performPrimitiveOperationUsingObject:andObject:] + 416
3 Foundation 0x0000000180ae9f18 -[NSComparisonPredicate evaluateWithObject:substitutionVariables:] + 364
4 Foundation 0x0000000180aec9b8 -[NSCompoundPredicateOperator evaluatePredicates:withObject:substitutionVariables:] + 208
5 Foundation 0x0000000180aec46c -[NSCompoundPredicate evaluateWithObject:substitutionVariables:] + 212
6 Foundation 0x0000000180aeca4c -[NSCompoundPredicateOperator evaluatePredicates:withObject:substitutionVariables:] + 356
7 Foundation 0x0000000180aec46c -[NSCompoundPredicate evaluateWithObject:substitutionVariables:] + 212
8 CoreData 0x000000018459b064 -[NSFetchedResultsController _preprocessUpdatedObjects:insertsInfo:deletesInfo:updatesInfo:sectionsWithDeletes:newSectionNames:treatAsRefreshes:] + 280
9 CoreData 0x000000018459be74 __82-[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:]_block_invoke + 1528
10 CoreData 0x00000001845402c0 developerSubmittedBlockToNSManagedObjectContextPerform + 156
11 CoreData 0x000000018454019c -[NSManagedObjectContext performBlockAndWait:] + 212
12 CoreData 0x000000018459b860 -[NSFetchedResultsController _core_managedObjectContextDidChange:] + 96
13 CoreFoundation 0x0000000180344990 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 140
14 CoreFoundation 0x00000001803448b4 ___CFXRegistrationPost_block_invoke + 84
15 CoreFoundation 0x0000000180343dbc _CFXRegistrationPost + 404
16 CoreFoundation 0x00000001803437c8 _CFXNotificationPost + 668
17 Foundation 0x0000000180b88ae0 -[NSNotificationCenter postNotificationName:object:userInfo:] + 88
18 CoreData 0x000000018453335c -[NSManagedObjectContext _postObjectsDidChangeNotificationWithUserInfo:] + 320
19 CoreData 0x0000000184545984 -[NSManagedObjectContext _createAndPostChangeNotification:deletions:updates:refreshes:deferrals:wasMerge:] + 1212
20 CoreData 0x0000000184534f04 -[NSManagedObjectContext _processRecentChanges:] + 2484
21 CoreData 0x0000000184536f58 -[NSManagedObjectContext save:] + 400
I may have solved the crash even if I do not know why the URI query failed. I'm assuming the persistent. history tracking probably isn't the issue or there's something more to it.
I think my issue was where the nspredicate came from that I was modifying.
To easily setup a fetch request I wrap it with a view container like this
DynamicFetchView(
predicate: nil,
sortDescriptors: sort,
fetchLimit: 10
) { (items: FetchedResults<MyItem>) in
...
}
Then using the mentioned function I would do items.nsPredicate = updateNSPredicate(for: query)
. I assume between updating the Tag with its new items and whatever else happens to the SwiftUI view as part of the data changing, then I hit this problem.
So I've added a state object to hold the predicate and pass that to the DynamicFetchView and update the state predicate instead.
@State private var predicate: NSPredicate? = nil
...
DynamicFetchView(
predicate: predicate,
sortDescriptors: sort,
fetchLimit: 10
) { (items: FetchedResults<MyItem>) in
...
}
...
predicate = updateNSPredicate(for: query)
It would be good to know why the URI 'contains' query breaks and string 'contains' does not. I can make some educated guesses, but at the very least I do not think I need to worry about this crash for now. I've tried dozens of times with no problems.