When we first added Core Data to our app, the tutorial we followed created an NSManagedObjectContext in our app delegate. It recommended that if we used Core Data on other threads, we should add an observer to update our main context when the thread's context was saved, like so:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:self.managedObjectContext];
This happens once in applicationDidFinishLoadingWithOptions:. However, the documentation for NSManagedObjectContext says:
Several system frameworks use Core Data internally. If you register to receive these notifications from all contexts (by passing nil as the object parameter to an addObserver… method), then you may receive unexpected notifications that are difficult to handle.
This suggests to me that the object parameter ought to be the context that's being saved (in my case the background context) rather than the main context. Is that the case? Do I need to observe NSManagedObjectContextDidSaveNotification from every NSManagedObjectContext I create?
Simon,
The object in the -addObserver:selector:name:object: method is the source of the notifications. In your case, it should be the background NSManagedObjectContext.
Should you observe every context you create? As in all things, that depends. For example, sometimes you want to use a context as a scratchpad and will never save anything from it. Why bother to observe it? In general, you want to observe any contexts that mutate the persistent store.
Andrew
Related
I have read plenty of info about code using a main thread managed object context and a lot about having private parents, children, and siblings for various reasons. I would like to know if there is any requirement to have more than a single private-queue managed object context that is set to use a "disk" based persistent store coordinator. I have almost no code in my app that will use the core data entities for updating the UI directly so anything more than a private-queue MOC seems excessive. My code will be happy to wait in the main thread for a background thread to get what little data it needs.
I need to be able to to grab objects from Core Data and keep them in a mutable array in memory in order to avoid constant fetching and slow UI/UX. The problem is that I grab the objects on other threads. I also do writing to these objects at times on other threads. Because of this I can't just save the NSManagedObjects in an array and just call something like myManagedObjectContext.performBlock or myObject.managedObjectContext.PerformBlock since you are not supposed to pass MOCs between threads.
I was thinking of using a custom object to throw the data I need from the CD objects into. This feels a little stupid since I already made a Model/NSManagedObject class for the entities and since the custom object would be mutable it still would not be thread safe. This means I would have to do something like a serial queue for object manipulation on multiple threads? So for example any time I want to read/write/delete an object I have to throw it into my object serialQueue.
This all seems really nasty so I am wondering are there any common design patterns for this problem or something similar? Is there a better way of doing this?
I doubt you need custom objects between Core Data and your UI. There is a better answer:
Your UI should read from the managed objects that are associated with the main thread (which it sounds like you are doing).
When you make changes on another thread those changes will update the objects that are on your main thread. That is what Core Data is designed to do.
You just need to listen to those changes and have your UI react to them.
There are several ways to do this:
NSFetchedResultsController. Kind of like your mutable array but has a delegate it will notify when objects change. Highly recommended
Listen for KVO changes on the property that you are displaying in your UI. Whenever the property changes you get a KVO notification and can react to it. More code but also more narrowly focused.
Listen for NSManagedObjectContextDidSaveNotification events via the NSNotification center and react to the notification. The objects that are being changed will be in the userInfo of the notification.
Of the three, using a NSFetchedResultsController is usually the right answer. When that in place you just change what you need to change on other threads, save the context and you are done. The UI will update itself.
One pattern is to pass along only the object ids, which are NSString objects, immutable and thus thread safe, and query on the main thread after those ids. This way every NSManagedObject will belong to the appropriate thread.
Alternatively, you can use mergeChangesFromContextDidSaveNotification which will update the objects from the main thread with the changes made on the secondary thread. You'd still need fetching for new objects, though.
The "caveat" is that you need to save the secondary context in order to get your hands on a notification like this. Also any newly created, but not saved objects from the main thread will be lost after applying the merge - however this might not pose problems if your main thread only consumes CoreData objects.
I'm using performBlock on my NSManagedObjectContexts so that my changes happen on the right queue for the given context. My question is - if I'm making a lot of changes and calling methods from within performBlock - is there an easy way to ensure that I use objects from the proper context.
Example:
I have an activeAccount iVar ( created on the Main Queue ) that is a NSMangedObject for the current account in the application. I have some instance methods that use the activeAccount object to perform certain tasks - getting data, setting data. So my question is if I am doing something on a background NSManagedObjectContext and I call one of these shared methods - is there a pattern I can use so that in these methods I know to either use the current activeAccount iVar or get a new one. Also, if I needed to do something that requires a NSManagedObjectContext - how do I know which one to get/use.
One method I have for knowing which NSManagedObjectContext to use is I have a method that checks if it is running on the current thread - it then knows to return the main thread's context or the background thread's context. Also, if I'm on the background thread, am I allowed to read the Object ID of the activeAccount that lives on the main thread so that I can get a copy of it on the background thread? Thanks in advance.
Brian,
Thread confinement can be a tricky proposition to maintain. The key thing you need to maintain is using objects in their proper MOC. As every managed object maintains a link to both its host MOC and its object ID, this is really easy to ensure. For example:
NSManagedObjectContext *newMOC = NSManagedObjectContext.new;
newMOC.persistentStoreCoordinator = oldActiveAccount.managedObjectContext.persistentStoreCoordinator;
ActiveAccount *newActiveAccount = [newMOC objectWithID: oldActiveAccount.objectID];
Now every instance you access from newActiveAccount is created in the newMOC and is, hence, thread confined to that MOC. objectIDs are persistent. The -persistentStoreCoordinator is rarely, if ever, changed on the mainMOC. Hence, the above code is properly confined. There are issues with the above technique if the source MOC is transient. Hence, I cannot guarantee the above code works with respect to two background MOCs.
Andrew
I have to ask first, why are you having so many contexts in use at the same time?
I use one for background operations and one for main thread. If I need to create another one for discardable changes, I'll just create it and pass it on, so now my self.managedObjectContext points to the draft context. I will never let my managed objects to live in a scope where they could access a multitude of contexts.
It is not entirely clear if you are writing for iOS or OSX, but with iOS for example:
If I need to push a new view controller into navigation stack I will initialize my destination view controller's managedObjectContext ivar as well as any NSManagedObject subclass instances. Since in -prepareForSegue: I know whether I'll create a draft context or just pass on my current one, I also know whether I need to initialize those managed object instances by referencing them by their IDs from newly created context or I can just pass them on.
Now inside my view controller I can take it for granted that my managed objects are always tied to the self.managedObjectContext.
My application has NSOperation subclasses that fetch and operate on managed objects. My application also periodically purges rows from the database, which can result in the following race condition:
An background operation fetches a bunch of objects (from a thread-specific context). It will iterate over these objects and do something with their properties.
A bunch of rows are deleted in the main managed object context.
The background operation accesses a property on an object that was deleted from the main context. This results in an 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault'
Ideally, the objects that are fetched by the NSOperation can be operated on even if one is deleted in the main context. The best way I can think to achieve this is either to:
Call [request setReturnsObjectsAsFaults:NO] to ensure that Core Data won't try to fulfill a fault for an object that no longer exists in the main context. The problem here is I may need to access the object's relationships, which (to my understanding) will still be faulted.
Iterate through the managed objects up front and copy the properties I will need into separate non-managed objects. The problem here is that (I think) I will need to synchronize/lock this part, in case an object is deleted in the main context before I can finish copying.
Am I missing something obvious? It doesn't seem like what I'm trying to accomplish is too out of the ordinary. Thanks for your help.
You said each thread has its own context. That's good. However, they also need to stay synchronized with changes to each other (how depends on their hierarchy).
Are the all assigned to the same persistent store coordinator, or do they have parent/child relationships?
Siblings should monitor NSManagedObjectContextObjectsDidChangeNotification from other siblings. Parents will automatically get notified when a child context saves.
I ended up mitigating this by perform both fetches and deletes on the same queue.
Great question, I can only provide a partial answer and would really like to know this too. Unfortunately your own solution is more of a workaround but not really an answer. What if the background operation is very long and you can't resort to running it on the main thread?
One thing I can say is that you don't have to call [request setReturnsObjectsAsFaults:NO] since the fetch request will load the data into the row cache and will not go back to the database when a fault fires for one of the fetched objects (see Apples documentation for NSFetchRequest). This doesn't help with relationships though.
I've tried the following:
On NSManagedObjectContextWillSave notification, wait for the current background task to finish and prevent new tasks from starting with something like
-(void)contextWillSave:(NSNotification *)notification {
dispatch_sync(self.backgroundQueue, ^{
self.suspendBackgroundOperation = YES;
});
}
Unset suspendBackgroundOperation on NSManagedObjectContextDidSave notification
However the dispatch_sync call introduces possible dead locks so this doesn't really work either (see my related question). Plus it would still block the main thread until a potentially lengthy background operation finishes.
Here's yet another question on Core Data and multithreading:
I'm writing an application on the iPhone that retrieves XML data from the internet, parses it in a background thread (using NSXMLparser) and saves the data in Core Data using its own NSManagedObjectContext. I have a class - let's call it DataRetriever - that does this for me.
There are different UIViewControllers that then retrieve the data to display it in their respective UITableViews, of course this happens on the main thread using NSFetchedResultsControllers and a single managed object context that is used for reading.
I've read the answer to this question, which tells me that I need to register for NSManagedObjectDidSaveNotifications on the background thread (this will be done by the DataRetriever class I suppose) and then call the mergeChangesFromContextDidSaveNotification method on the reading context from that class on the main thread. This, I think, is totally thread-unsafe. I might have interpreted this the wrong way, though.
I've also read this part of Apple's documentation on the subject (Track Changes in Other Threads Using Notifications), and it tells me to simply register for NSManagedObjectDidSaveNotifications coming from the reading context in the view controller on the main thread and then it would have to call mergeChangesFromContextDidSaveNotification to update its reading context.
I went with Apple's recommendations: I now have my view controllers register themselves to NSManagedObjectDidSaveNotifications on the main thread using the reading managed object context as the source of the notifications. Doing this on the writing context probably isn't thread safe, and Apple's documentation isn't very specific on this.
Result: No crashes, but I am not receiving any notifications either.
Side note: I've read in Apple's documentation that notifications don't automatically propagate to other threads and I might even be listening for notifications from the wrong context, but why is Apple telling me to do it this way, then?
Any help is greatly appreciated.
-- EDIT --
Just to be clear, I'm registering for notifications coming from a particular NSManagedObjectContext, Apple's documentation specifically states (here) that some system frameworks may use an instance of Core Data themselves, so I could be receiving notifications from contexts that don't concern me if I don't specify a source. The documentation I referred to earlier on doesn't say anything about this, though. Any comments on this design choice are welcome.
The UI runs on the main thread so you want any intensive processing that might bog the UI down done on another thread. You have the context in the main thread listen for notifications because the main thread context is usually the only one that needs to update itself because of changes by other context in other threads.
All this is thread safe because data won't be deleted from the persistent store as long as one or more context is still using it. So, if context A has an object with the data while context B deletes another object representing the same data, the object in context A remains alive until context A calls for a merge.
Basically, each context operates in its own little world until you call merge. The race conditions that normally bedevil thread based data operations don't occur with Core Data.