Retrieving an NSManagedObjects array using objectID returns an empty array

I am trying to fetch to extract managed objects from a context using an array of object identifiers that I have compiled from a separate context. However, the selection returns with an empty array.

From the "Extracting Specific Objects" section of the "Master Data Programming Guide" link :

If your application uses several contexts and you want to check whether the object has been deleted from persistent storage, you can create a query for the selection with the predicate of the form self ==% @. The object you pass as a variable can be either a managed object or an identifier for a managed object ... "

If you need to check for the presence of several objects, it is more efficient to use the IN operator than to perform several selections for individual objects, for example:

NSPredicate * predicate = [predicate NSPredicateWithFormat: @ "self IN% @", arrayOfManagedObjectIDs];

While we are talking about testing to delete an object, I assumed that if the objects are not deleted, the result array is not empty and will contain the actual NSManagedObjects. However, when I execute this code:

- (NSArray *)managedObjectsOfEntityName:(NSString *)entityName fromObjectIDs:(NSArray *)objectIDs managedObjectContext:(NSManagedObjectContext *)managedObjectContext { NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName]; request.predicate = [NSPredicate predicateWithFormat:@"self IN %@", objectIDs]; __autoreleasing NSError *error = nil; NSManagedObjectID *testID = objectIDs[0]; NSManagedObject *obj = [managedObjectContext existingObjectWithID:testID error:&error]; if (!obj) { NSLog(@"Unable to perform fetch. Error: %@", error); } error = nil; NSArray *results = [managedObjectContext executeFetchRequest:request error:&error]; if (!results) { NSLog(@"Unable to perform fetch. Error: %@", error); } return results; } 

The results array is non-zero and empty, and obj correctly populated.

I added an explicit call to existingObjectWithID: as a health check, and it returns with the expected object without errors.

Here's the debugger output for the relevant variables:

 (lldb) po entityName Foo (lldb) po objectIDs <__NSArrayI 0x1170d4950>( 0xd0000000055c0082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p343>, 0xd000000008e80082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p570>, 0xd000000008840082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p545>, 0xd000000006040082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p385>, 0xd000000007740082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p477>, 0xd000000008280082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p522>, 0xd000000008e40082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p569> ) (lldb) po request <NSFetchRequest: 0x10f338840> (entity: Foo; predicate: (SELF IN { 0xd0000000055c0082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p343>, 0xd000000008e80082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p570>, 0xd000000008840082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p545>, 0xd000000006040082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p385>, 0xd000000007740082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p477>, 0xd000000008280082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p522>, 0xd000000008e40082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p569>}); sortDescriptors: ((null)); type: NSManagedObjectResultType; ) (lldb) po request.predicate SELF IN { 0xd0000000055c0082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p343>, 0xd000000008e80082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p570>, 0xd000000008840082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p545>, 0xd000000006040082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p385>, 0xd000000007740082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p477>, 0xd000000008280082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p522>, 0xd000000008e40082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p569>} (lldb) po testID 0xd0000000055c0082 <x-coredata://CEB1EDA5-7F7A-4342-85E5-6C2E261308CC/Foo/p343> (lldb) p testID.isTemporaryID (bool) $7 = false (lldb) p obj (NSManagedObject *) $9 = 0x000000010f338630 (lldb) po results <__NSArrayI 0x10f205c70>( ) (lldb) po testID.entity nil 

nil entity on testID weird, but I'm not sure what is bad.

So, I clearly don’t understand why the selection returns empty. The name of the object is correct, the selection and predicate look good, and the object is in context, but still zero results.

Additional context:

Essentially, the bigger picture is that I have a background operation that uses its own Managed Object Context (MOC) to do some work. The results of this work are necessary for the main queue, so I pack the identifiers of the objects obtained as a result of the work and transfer them to the main queue. In the main queue, I need the actual managed objects, so I'm trying to get them, by object ID, from the MOC main queue. I understand that I can use objectWithID: or existingObjectWithID:error: or even objectRegisteredForID: in the MOC to get these objects, but each of them has its own special problems:

  • objectWithID: can return an object that is fictitious if the object is not in the context, and if it is in the context, it will return an error.
  • existingObjectWithID:error: excellent, since we will return nil (and not a dummy object), but it also returns an error.
  • objectRegisteredForID: will return nil if the object is no longer in context.

So, if I use either objectWithID: or existingObjectWithID:error: in a loop to get a bunch of objects, potentially n sent to the stored storage so that the objects crash, which means that the performance will be potentially terrible. If I use objectRegisteredForID: I might not get the object at all if it is not already in the main MOC queue.

So, instead of trying to iterate through the array, I expected the query to fetch would limit the overhead of interacting with the persistance repository and return all the objects I needed in one fell swoop.

Addition:

Aside, the problem really seems to be related to the predicate @"self IN %@" , since I can remove this predicate and get all entityName objects without problems. I also tried @"objectID IN %@" as a predicate with the same (null) results.

+6
source share
1 answer

Okay, so ready to move away from the Core Data rabbit well?

TL DR

NSManagedObjectID , whose permanent repository coordinator is no longer in memory, lose their NSEntityDescription ( entity ) and do not equate ( isEqual: returns NO ) to NSManagedObjectID from another persistent repository coordinator, although their URIRepresentation the same.

Down the rabbit hole

Sweet ... here we go.

Remember, I collect an array of object identifiers from a separate managed object (MOC) context in a separate thread? Good. However, I did not mention that this MOC uses its own persistent storage coordinator (PSC) (of course, pointing to the same file). The fact that PSC and MOC are short-lived, because after the work is done they are auto-implemented, but while they are alive, I save the NSManagedObjectID instances in NSArray (which is transferred to another thread).

After receiving in a separate thread, NSManagedObjectID has an nil object. This seems to be happening (an educated guess here ...) because the PSC from which these object identifiers were derived is now no longer in memory, and NSManagedObjectID should contain a week reference to NSEntityDescription ( entity ), which should be held PSC nil entity seems to be causing problems as suspects suspected ...

I can’t be sure of the internal functions, but it would turn out that without an entity NSManagedObjectID not equal to another NSManagedObjectID with the same URIRepresentation . Let me illustrate:

In the following code: * objectID is an NSManagedObjectID * from persistent storage that is no longer in memory and whose property is nil . * managedObjectContext - our MOC on our current thread (of course)

 NSURL *URIRep = objectID.URIRepresentation; NSManagedObjectID *newObjectID = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:URIRep]; 

If we look at this in the debugger ... guess what:

 (lldb) p [ojectID isEqual:newObjectID] (bool) $0 = false 

Although the URIRepresentation these two object identifiers must be the same, the objects are not equal.

This is almost certainly why the predicate [NSPredicate predicateWithFormat:@"self IN %@", objectIDs] cannot match any objects and forces the select query to return null objects.

It is interesting, however, that in the following code, where testID is NSManagedObjectID * from persistent storage that is no longer in memory and whose property is nil , the object is retrieved from the MOC without any problems:

 NSManagedObject *obj = [managedObjectContext existingObjectWithID:testID error:&error]; 

Bypass

There are two things that I checked as a workaround for this:

  • Use the same persistent storage coordinator (PSC) when communicating NSManagedObjectID between contexts (and make sure it stays in memory). If this is done, NSManagedObjectID will never lose their entity , and everything will work as expected.

  • Instead of passing an NSManagedObjectID around the context to the context, use URIRepresentation . that is, instead of passing an array of NSManagedObjectID objects, pass an NSURL array representing the URIRepresentation for each NSManagedObjectID . Once you are ready to fetch with these object identifiers, use the managedObjectIDForURIRepresentation: message to get the NSManagedObjectID from the current PSC MOC.

Option 1 is simpler, but may have some drawbacks with concurrency time and may be too strict if, for example, you wanted to have a separate PSC for your operations that was just read.

In my (relatively limited) experience calling managedObjectIDForURIRepresentation: it looks very productive, so converting a URIRepresentation NSURL to NSManagedObjectID does not seem to add much overhead.

thanks

Thanks for continuing ... Hope this helps explain the problem and workarounds. Of course, I feel that I have earned the dignity badge in the identifiers of Core Data objects and the constant coordinators of stores, breaking through all this.

Greetings

Levy

+12
source

Source: https://habr.com/ru/post/972847/


All Articles