NSpredicate crash on 'Can't use in/contains operator' sometimes only after save

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.

Accepted Answer

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.

NSpredicate crash on 'Can't use in/contains operator' sometimes only after save
 
 
Q