Core Data - Shared code called from NSManagedObjectContext peformBlock: - core-data

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.

Related

Can I get by with a single privateQueueConcurrencyType managed object context and no MOC for the main queue?

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.

Read/Write custom objects on multiple threads

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.

Core Data: Deleting causes 'NSObjectInaccessibleException' from NSOperation with a reference to a deleted object

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.

Do I need to observe NSManagedObjectContextDidSaveNotification from every NSManagedObjectContext I create?

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

Resolving an NSManagedObject conflict with multiple threads, relationships, and pointers

I'm having a conflict when saving a bunch of NSManagedObjects via an outside thread. For starters, I can tell you the following:
I'm using a separate MOC for each thread.
The MOCs share the same persistent store coordinator.
It's likely that an outside thread is modifying one or many of the records that I'm saving.
OK, so with that out of the way, here's what I'm doing.
In my outside thread, I'm doing some computation and updating a single value in a bunch of managed objects. I do this by looking up the object in the persistent store by my primary key, modifying the single decimal property, and then calling save on the bunch all at once.
In the meantime, I believe the main thread is doing some updating of its own.
When my outside thread does its big save on its managed object context, I get an exception thrown stating a large number of conflicts. All of the conflicts seem to be centered around a single relationship on each record. Though the managed object in the persistent store and my outside thread share the same ObjectID for this relationship, they don't share the same pointer. Based on what I see, that's the only thing that's different between the objects in my NSMergeConflict debug output.
It makes sense to me why the two objects have relationships with different pointers -- they're in different threads. However, as I understand it from Apple's documentation, the only thing cached when an object is first retrieved from the persistent store are the global IDs. So, one would think that when I run save on the outside thread MOC, it compares the ObjectIDs, sees they're the same, and lets it all through.
So, can anyone tell me why I'm getting a conflict?
Per the documentation in the Concurrency with Core Data chapter of The Core Data Programming Guide, the recommended configuration is for the contexts to share the same persistent store coordinator, not just the same persistent store.
Also, the section Track Changes in Other Threads Using Notifications of the same chapter states if you're tracking updates with the NSManagedObjectContextDidSaveNotification then you send -mergeChangesFromContextDidSaveNotification to the main thread's context so it can merge the changes. But if you're tracking with NSManagedObjectContextDidChangeNotification then the external thread should send the object IDs of the modified objects to the main thread which will then send -refreshObject:mergeChanges: to its context for each modified object.
And really, you should know if the main thread is also performing updates through its controller, and propagate its changes in like manner but in the opposite direction.
You need to have all your contexts listening for NSManagedObjectContextDidSaveNotification from any context that makes changes. Otherwise, only the front context will be aware of changes made on the background threads but the background context won't be aware of changes on the front thread.
So, if you have three threads and three context each of which makes changes, all three context must register for notifications from the other two.
Unfortunately, it seems as though this bug was actually being caused by something else -- I was calling the operation causing the error more than once at the same time when I shouldn't have been. Although this doesn't answer the initial question as to why pointers matter in conflicts, updating my code to prevent this situation has resolved my issue.

Resources