unexplained behavioural difference between objectWithID: versus existingObjectWithID: - ios

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).

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.

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?

Getting the "existing object" using existingObjectWithID before getting the "crash object" using objectWithID will lead to both objects being equal.
The other way round the app will crash as soon as a property of the "crash object" is accessed. In this case the objects are not equal. The "crash object" has a temporary ObjectID.

Related

EXC_BAD_ACCESS on mergeChangesFromContextDidSaveNotification

We have been trying to debug a Core Data multiple-context/threading issue wherein merging a Core Data save notification into our main thread NSManagedObjectContext is sporadically crashing the app. This is crashing ~2% of our app sessions and we are at a loss as to how to solve this. We would really appreciate any guidance or general advice on what could possibly cause this crash.
We have a Core Data setup that looks like this:
N.B. This is the default Core Data stack in Magical Record v2.3 created from [MagicalRecord setupAutoMigratingCoreDataStack]
This is the scenario where our app is crashing:
HTTP request returns JSON
JSON is parsed into NSManagedObjects (Some new entities, some updated entities) on Root Saving Context
Root Saving Context saves to persistent store
NSManagedObjectContextDidSaveNotification is broadcast by Core Data. Default context on main queue observes this and calls mergeChangesFromContextDidSaveNotification: with the NSDictionary of changes on the main thread.
It crashes when objectID is sent to an invalid object (most likely NSManagedObject has been deallocated).
This is occurring inside the private implementation of NSManagedObjectContext mergeChangesFromContextDidSaveNotification: so it is impossible for us to see what has actually gone wrong here; all we can tell at this point that an object which should exist, does not.
This only happens on a small percent of Core Data saves, indicating that may not be a fundamental flaw in our Core Data → API stack. Moreover, there is no indication that the size or type of the changes (insertions/updates/deletions) in the context changes have any impact on the likelihood of the crash.
The documentation of NSManagedObjectContextDidSaveNotification says that:
"You can pass the notification object to mergeChangesFromContextDidSaveNotification: on another thread, however you must not use the managed object in the user info dictionary directly on another thread. For more details, see Concurrency with Core Data in Core Data Programming Guide."
Maybe this is the issue? I would make sure the object u get from the notification is getting saved on Default Context on the same thread it was posted by Root.
It's been some time now since this question was posted and after rediscovering it I'd like to answer my own question for the sake of others who find this thread.
In my circumstance I had migrated a large code base from sibling NSManagedObjectContexts updated via NSManagedObjectContextDidSaveNotification's. However the problem was not really anything to do with this, even though this did expose the issue.
The real cause of this were places that there were older parts of the code, setup by previous engineers, that had setup KVO on NSManagedObjects and their properties. It transpired that KVO on Core Data entities is in fact a very very bad idea.
More accurately, it appeared that this happened when KVO was setup on entities and either the object, or the target of a relationship on this object was deleted from the NSPersistentStore. This second condition seemed to not be the only cause of the issue, but was definitely a very prominent cause in my situation.
Lesson's learnt:
Use a fetched results controller when you need to. KVO is not a convenient shortcut and you shouldn't avoid migrating dodgy Core Data KVO code to NSFetchedResultsControllers or another sensible alternative as the procrastination will just hurt you.
Multi threaded Core Data is a difficult but very worthwhile skill to become an expert in. Knowing your Core Data stack and the nuances and limitations of Core Data multithreading is absolutely worth all the mental anguish.
One possibility is that your persistent store has become corrupted and is in an inconsistent state. If this happens an error code is generated which Magical Record does not necessarily deal with. This can be the source of a number of difficult-to-repeat apparently-random crashes related to Magical Record (and may or may not be considered a Magical Record bug).
It's worth reading the Magical Record issues threads here (same issue) and here (different issue, but could be similar cause). When I hit these problems I managed to make some temporary patch fixes following various hints in those threads, but ultimately I decided to remove my dependency on Magical Record, and I have had no problems since then.

MagicalRecord MR_saveOnlySelfAndWait doesn't work within saveWithBlock

I'm getting the grips with MagicalRecord 2.3 and it seems as though if I run MR_saveOnlySelfAndWait within a saveWithBlock, nothing persists (the callback has a success flag of NO and error is nil).
However, when I run MR_saveToPersistentStoreAndWait within that block it works no problem. The object in question only has one attribute (a String) and it's set.
Is this due to the fact that saveWithBlock runs on a background thread anyway? Pretty confused here.
MagicalRecord is designed to work with nested contexts. Te rule with regard to nested contexts is that if a context does not have a parent, then calling save will persist changes to the store. If there is a parent, then a save will only persist changes to the parent, and not to the store. This is the reasoning behind the two methods. Choose the proper save for your situations

How to make NSManagedObject not fault?

I'm currently debugging a big project written by another developer. The project uses CoreData which i am very new to. I'm having a crash which happens due to the fact that some NSManagedObject is being a fault(i have poor understanding of what fault is) and i would like to convert the object into "not fault" and see if it helps. Reading documentation made me think that to make object not fault is equal to to fire fault (again i have poor understanding of what is "to fire"), so i decided to fire it by calling any method on it, and this method is hasChanges since it's not in the list of methods which don't fire fault. However even after calling this method the object still remained being fault. Can anyone give me an example of how to convert NSManagedObject into "not fault" state?
The exception you mentioned in a comment is:
Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x1f0627a0 <x-coredata://E40418A0-A8E5-4340-865F-A9DA2E0095DD/CoreObject/p288>''
The "could not fulfill a fault" message indicates that this is not simply a problem with firing the fault. In fact there are no special steps to fire a fault-- you just access the attributes, and if necessary, the fault fires automatically.
What this error is telling you is that you're doing something that causes a fault to fire, but that Core Data can't find any record of the instance you're using. This can happen in certain scenarios, for example:
Fetch an object, and leave it as a fault (i.e. don't access any attributes)
Delete it from Core Data, but keep a reference to the object (maybe in an instance variable)
Save changes
Try to access an attribute on the object you fetched in step 1.
At this point Core Data would normally use the object to look up the attribute value. But you already deleted it, so the attribute data is gone. Core Data throws this exception.
There are some other scenarios that can trigger this-- calling reset on a managed object context while keeping previously fetched objects around, or removing the persistent store (so that the data still exists in the store file, but the file isn't loaded anymore). In general it means Core Data is trying to look up data on an object that's no longer valid.
What you need to do:
Figure out which object is causing this. Setting an exception breakpoint, so that the debugger loads just as the crash is happening, is probably a good idea.
Get rid of that object. Preventing the fault from firing might prevent this specific crash, but as long as you still have this object, it's like a land mine in your app. It will explode and crash the app as soon as you touch it.
Figure out why you have invalid managed objects lurking in memory. Maybe you should have gotten rid of them earlier? Maybe you're accidentally deleting something you don't want to delete? For some reason you're keeping managed objects around when they're no longer valid. This is the core problem that's causing your trouble.
Faulting is the process whereby an object and/or its properties are fetched from the database on-demand.
For example, if you attempt to access a property or relation on some object, person.name, that object may not have its data in-memory, and it has to be fetched from the underlying data store. This is a fault.
Faulting is a normal part of how Core Data works, and should not be related to crashes. An object will be unfaulted when you try to access its properties and relations.

Can you edit the same NSManagedObject in 2 different ManagedObjectContexts and merge their changes?

I'm syncing with a MySQL database.
Initially, I was going to loop through all my new/modified objects and set all the foreign keys for that object and then do the next object, and so on... But that's a lot of fetch requests.
So instead I wanted to loop through all my new/modified objects and set the foreign keys one at a time. So the first pass over my objects sets fk1, my next sets fk2, so on...
Cool, fetch requests drastically reduced. Now I'm curious if I could thread these fk setters. They aren't dependent on each other, but they are modifying the same object, even though they're only setting one relationship, and it's a different relationship. Speaking in git terms, these changes could be 'merged' together without any conflict, but is it possible to push changes in one child managedObjectContext(childContext:save) up to the parentManagedObjectContext(parent:performBlock^{parent:save}) and pull it down in another, different child managedObjectContext(???)? Or will the merge policy only take one childContext's version of the object and leave the other fks effectively unchanged.
I know this exists: NSManagedObjectContext/refreshObject:mergeChanges:
But that's on an object by object level. Will that cause a bunch of fetches? Or will that update my whole context at once/in batches?
Following Apple's suggestion from here:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html
I've created/updated my values before I start setting any relationships, so all entities already exist before I try to point any relationships at them.
Aside: We have a couple apps that could benefit from the concurrency, because they throw a considerable amount of data around, and with the quad core iPad apps, this would really help out with the time the initial sync takes.
I'n not sure what you are trying to do and why (you could write less lines in your question and be more clear), but here are some guidelines for working with Core Data:
-- NSManagedObjectContext is not thread safe. Therefore, you need to limit your access to this managed object context to happen inside 1 thread. Otherwise, you may end up having many errors you can't understand.
-- NSManagedObjectContexts apart from doing other things, serve like "snapshots" of your persistent store. That means that when you change an object, you save it to the persistent store, you post a NSManagedObjectContextDidSaveNotification and then call mergeChangesFromContextDidSaveNotification: in another place inside your program in order to load the latest data from the persistent store. Be careful of thread safety.
NSManagedObjectContext/refreshObject:mergeChanges: according to apple is not about just refreshing a managed object. If you pass YES as the second argument, it will write any pending changes from this managed object context to the persistent store, and will load any other changes to other properties of this object from the persistent store, thus "synchronizing" your object with the persistent store. If you pass NO as the second argument, the object loses any pending changes, and it is being turned to a fault. That means that when you attempt to access it, the Managed Object Context will reload the object as it was last saved to the database. It will NOT reload the entire managed object context. It will only operate on the object.
Aside: I have written a blog post that scratches the surface of asynchronous loading from a core data database. In my case, since I'm doing heavy lifting with the database, I ended up using an NSOperation that operates with its own NSManagedObjectContext, and using serial GCD queues to save large chunks of data, since it was faster than having multiple threads accessing the same persistent store, even if they operate on different managed object contexts.
I hope I helped.

Avoiding registered object buildup (memleak) in NSManagedObjectContext

I have a memory-intensive iOS app and I'm working on making sure that memory usage does not build up over time. My app has a "main" context that lives for the lifetime of the app, with other smaller contexts being spawned occasionally for background tasks.
One thing I have noticed is that NSManagedObjects appear to remain registered in the main context long-term and the only way to truly reclaim all the memory associated with pulling the objects from the DB is to call [NSManagedObjectContext reset].
This of course results in a nice drop in memory usage as all the registered objects from recently closed list views are properly ejected from memory, however it is annoying because you have just invalidated every object that was registered in that context that you still have a reference to (i.e. objects that are refered to by views that are still open), and you now need to re-fetch all the these objects from the database to avoid exceptions for accessing an invalidated object.
Is this the only way of flushing out the registered object set from an NSManagedObjectContext, or is there a better way that successfully ejects all the registered objects you no longer have references to, but doesn't invalidate all the NSManagedObjects that are still alive?
NSManagedObjectContext has an internal row cache, and the only way you can clear that out is by resetting the context. If you're actually experiencing memory issues, a few things that may help are:
Managed objects retain their related objects. If you have managed object A that's referenced by a relationship from some other managed object B, then object A will remain in memory even if you've released all references to it. It won't actually be deallocated until object B is deallocated or re-faulted, because it will still be retained by B.
One way of dealing with this (and other memory issues) is by calling refreshObject:mergeChanges: on the MOC for objects you aren't currently using, with the second argument set to NO. That re-faults the object, i.e. makes the object go back to the initial "fault" state, unloading its property values and relationships. With A and B from before, re-faulting B would release the relationship to A. Keep in mind this will lose any unsaved changes on B, so make sure you've saved first if necessary.
If your managed objects contain any kind of large binary data, try moving that data into a separate entity or out of Core Data altogether to avoid loading it into memory when it's not needed.
reset on the MOC is basically the nuclear option when it comes to Core Data memory management. It's extremely effective but as you've found can be very dangerous. It's best avoided unless you already aren't using any objects loaded from the MOC.

Resources