NSPredicate not executed

2020-02-26 00:20发布

问题:

This is quite funny. In my application I create thousands of entry in the database (in another thread, I'm using MagicalRecord). Everything seems working fine (from a background/foreground/context point of view).

When, in the main thread, I try to fetch the "just inserted" data, I discovered the following behaviour:

- (NSArray *) familiesInCompany:(Company *) company {
  NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@"company == %@", company];
  NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"company.name == %@", company.name];

  NSArray *first = [Family MR_findAllSortedBy:@"name" ascending:YES withPredicate:predicate1];
  NSArray *second = [Family MR_findAllSortedBy:@"name" ascending:YES withPredicate:predicate2];
  NSArray *third = [Family MR_findByAttribute:@"company" withValue:company andOrderBy:@"name" ascending:YES];

  return second;
}

Now what I get is:

  • first: is an empty array
  • second: contains all the Family objects, as expected
  • third: is an empty array.

By debugging the SQL statement I get the following:

The "first" statement:

CoreData: annotation: total fetch execution time: 0.0000s for 0 rows.

The "second" statement":

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZCOMPANY FROM ZFAMILY t0 JOIN ZCOMPANY t1 ON t0.ZCOMPANY = t1.Z_PK WHERE t1.ZNAME = ? ORDER BY t0.ZNAME

CoreData: annotation: sql connection fetch time: 0.0005s

CoreData: annotation: total fetch execution time: 0.0007s for 2 rows.

The "third" statement:

CoreData: annotation: total fetch execution time: 0.0000s for 0 rows.

The hilarious thing is that I close the application (I mean really manually terminate it) and and I open it back, all the three "fetching" statements work.

Why the first and the third fetch statements seem to never being executed? How to dig into the problem?

回答1:

I had this same issue, this is what I figured out, and how I resolved it.

Magical Record has a root NSManagedObjectContext as a parent of the default NSManagedObjectContext. When I create an NSFetchedResultsController in the default context, everything seems to work fine, just like you.

The problem is that all the new NSManagedObject's come back with their still-temporary ObjectID's. So, in my case, I was using an NSPredicate to scope a query on an associated table. I didn't just call the association method because I didn't want to load everything into memory and wanted NSFetchedResultsController to handle changes, for me.

With the temporary ObjectID the query finds zero results and that's exactly what it displays.

Apparently the child context (default) doesn't get the benefit of the transformation to non-temporary ID's, even though it's been persisted to the backing store.

Even worse badness happened when I tried to force the issue with obtainPermanentIDsForObjects:error:. Core Data complained that it could not satisfy a fault for my instance. Nevermind, there's no way it was actually a fault. Simply refreshing the object had no effect, either. I suspect this is a Core Data bug that hardly no one tickles because they just use the association methods to get an NSSet.

My fix was to use the parent context for the NSFetchedResultsController, just like in this question, Magical Record, saving, and NSFetchedResultsController.

I was already wrapping the default in a new child context upon edit and, therefore, copying the instances into that editing context with createInContext, so I didn't have to do any extra work beyond just adding .parentContext to the argument.

Incidentally, this only ever happened on new instances of the source of the association. Once an instance was there from startup, it had a non-temporary ObjectID and never had the issue.