Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unexplained behavioural difference between objectWithID: versus existingObjectWithID:

Tags:

ios

core-data

I understand the documented differences between these two calls. However does anyone know the reasons for the following observed behaviour I have noticed:

If I have a parentContext and a temporary childContext where I use the childContext to edit, insert and deleted objects, if use [childContext objectWithID:objectID]; to retrieve a known existing managed object, present in the parent context, it will sometimes give me an object with a fault that on being fired fails and generates an exception. I understand objectWithID: will, by design, always return an object in a faulted state regardless of if an actual managedObject exists for the given objectID. However if the object actually exists in the parent context, I would expect that when any of the properties are accessed, the object will always be successfully retrieved from the parent context (e.g. the fault will be fired) without any problem. If I use [childContext existingObjectWithID:objectID]; I find it does indeed always succeed.

For the record, I have turned off caching on the child context and this same behaviour occurs after [childContext resetContext] has been called - so it's not an artefact of old cached data hanging around that is inconsistent with the parent context.

The documentation alone seems to me to be insufficient to explain this behaviour. I can of course chalk it up to experience and just say "I now know to always use existingObjectWithID: when passing object IDs to my child edit context perform block" but I feel uneasy and would like to understand exactly what is going on here (not least so I can understand if there is any performance impact of using the one as over the other but also to understand what the constraint is so I can ensure there isn't some bad practice I'm unnecessarily implementing in my code and then using a wrong or inefficient call to fix it).

like image 201
TheBasicMind Avatar asked Nov 19 '12 11:11

TheBasicMind


2 Answers

I've seen this behavior in my own code (the stack traces were from Mars, took forever to track down the cause of the issue), and my solution was the same (move from use of objectWithID: in the child context to use of existingObjectWithID:).

In my case, I'd create an object on the parent context, immediately obtain a permanent object ID for it, and ask for a save using UIManagedDocument's updateChangeCount:UIDocumentChangeDone, which will schedule a save at some point in the future. I think this point is key for the discussion.

I'd then perform a network call to obtain XML data pertaining to the newly-created object, and parse and import that data into Core Data. This occurred on a background thread, and I'd pass the object ID to the thread, which used a thread-confined child import context.

Under iOS5, I'd use objectWithID: in the worker thread to fault the new object into the import context. Worked fine, no issues.

Under iOS6, this began to fail, with bizarre stack traces, and the only solution was to move to existingObjectWithID:. Under testing, it appears to be a solid solution with this change in place.

Like you, I've tried to find some definitive statement as to why this works, but I've been unsuccessful in doing so. However, I think the pattern that my code exhibited and the stack traces I was seeing perhaps explains what's happening. I was always seeing a crash attempting to obtain data from the persistent store. I believe that under iOS5, the save I requested via UIManagedDocument was occurring before my child thread got a chance to run, and thus the new object was persisted before I called objectWithID:. It would appear from my crash logs that that is not the case under iOS6; the thread runs before the object has made it to the persistent store, and thus it can't be fetched into the child context yet. My assumption is that existingObjectWithID: is ensuring that any pending SQL I/O is performed before attempting a fetch, and thus the persistent store is consistent when the fetch occurs in my worker thread. I have been unable to find anything definitive to support this, but extensive testing seems to support that that's what's happening.

like image 51
Allan Bazinet Avatar answered Oct 31 '22 06:10

Allan Bazinet


Having the same issue as the OP, and while I think the exception is by design as per

If the object is not registered in the context, it may be fetched or returned as a fault. This method always returns an object. The data in the persistent store represented by objectID is assumed to exist—if it does not, the returned object throws an exception when you access any property (that is, when the fault is fired). The benefit of this behavior is that it allows you to create and use faults, then create the underlying data later or in a separate context.

in the Apple docs -- that is, the data is NOT in the persistent store yet, it's sitting in the parent context -- it still seems odd that I simply cannot get my realized object in the parent context. Why must the persistent store have to know about it, for my object to be populated?

like image 44
h4xnoodle Avatar answered Oct 31 '22 05:10

h4xnoodle