Is it valid to mix the "1 moc per thread" strategy with "child moc" strategy? - ios

Until now I always used a "main moc" for the main thread, initialised like this:
[[NSManagedObjectContext alloc] init];
and then I have NSOperation subclasses with their own moc that import data from the webservice, and I merge in the "main" moc on save observing NSManagedObjectContextDidSaveNotification
But now I need the ability to add "temporary" objects that the user can commit (or not) later. So it looks like a child context is the perfect fit, and in order to use child context I changed the initialization of my "main moc" to
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
The question is: can my current structure with NSOperation subclasses with their own moc (initialised without a type in their own thread) have problems if used along with the child context strategy? I don't think so, but I don't find much about mixing those strategies.
Note that I want to maintain the NSOperation subclasses and I don't want to use child contexts also for importing my data, because it suffers on performances, see http://floriankugler.com/blog/2013/4/29/concurrent-core-data-stack-performance-shootout
Moreover, when I create a new child of my main thread (that is of type NSMainQueueConcurrencyType), can I create it that child with type NSMainQueueConcurrencyType, continuing to work with my objects in the main thread as usual? Or am I forced to use NSPrivateQueueConcurrencyType, and use performBlock: for every operation on my objects?
I'm asking because is not clear from the documentation if using 2 moc on the same thread (the main thread in this case) could be a problem.
UPDATE:
Finally I implemented and used this solution on production and there are not problems so far. The only thing that I needed to do is to avoid merging on my NSManagedObjectContextDidSaveNotification notification when the moc has a parentContext (we don't want to merge mocs with a parent context, because they manage the merge themselves, but obviously the notification is triggered also for save on this kind of moc)

Yes, you can have multiple main queue context mocs, for exactly the reason you say - you create a temporary editing context for editing data which is then saved or discarded depending on user action.
As for mixing and matching with your operation queue contexts - that shouldn't be a problem. If you're merging back to the parent context, then any child contexts will pick up that data the next time they fetch.

Actually, that's indicated when working with multiple threads. Here, I've wrote an article exactly about this. The slave mocs mentioned in it, are designed exactly for working with operations, each operations on it's own slave moc.

Related

iOS: How do Core Data Merge policies affect NSManagedObjectContext Save and Refresh operations

I have been reading about merge policies and it gives me conflicting ideas about when changes are merged.
I am having two contexts - one in background queue and one in main queue. Both have policies defined NSOverwriteMergePolicy which I think is outright wrong. I am already having problems in sync. I often witness that in background sync from server data my NSManagedObjects are often outdated, and I end up saving them, causing loss of data.
Is there any place I could visit all the rules & order of precedence for context save, context refresh with respect to overriding policies?
I have seen all the documentation around merge policies but I am confused whether they take effect upon SAVE or REFRESH.
Also, up to some extent, this is very confusing. For example, Apple Docs state this for NSMergeByPropertyObjectTrumpMergePolicy:
A policy that merges conflicts between the persistent store's version
of the object and the current in-memory version by individual
property, with the external changes trumping in-memory changes.
The merge occurs by individual property.
For properties that have been changed in both the external source and in memory, the in-memory
changes trump the external ones.
How to ensure that my desired properties get modified / remain unaffected when I choose to modify / not modify them on different contexts?
TL:DR: Merge policy is evil. You should only write to core-data in one synchronous way.
Full explanation: If an object is changed at the same time from two different contexts core-data doesn't know what to do. You can set a mergePolicy to set which change should win, but that really isn't a good solution, because you will lose data that way. The way that a lot of pros have been dealing with the problem for a long time was to have an operation queue to queue the writes so there is only one write going on at a time, and have another context on the main thread only for reads. This way you never get any merge conflicts. (see https://vimeo.com/89370886 for a great explanation on this setup).
Making this setup with NSPersistentContainer is very easy. In your core-data manager create a NSOperationQueue
_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;
And do all writing using this queue:
- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
void (^blockCopy)(NSManagedObjectContext*) = [block copy];
[self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext* context = self.persistentContainer.newBackgroundContext;
[context performBlockAndWait:^{
blockCopy(context);
[context save:NULL]; //Don't just pass NULL here. look at the error and log it to your analytics service
}];
}]];
}
When you call enqueueCoreDataBlock the block is enqueued to ensures that there are no merge conflicts. But if you write to the viewContext that would defeat this setup. Likewise you should treat any other contexts that you create (with newBackgroundContext or with performBackgroundTask) as readonly because they will also be outside of the writing queue.

Concurrency in CoreData with multiple contexts

There are few ways of handling concurrency in CoreData
One of them is using parent/child managedObjectContexts like so:
let mainContext = NSManagedObjectContext( concurrencyType: .MainQueueConcurrencyType)
let childContext = NSManagedObjectContext( concurrencyType: .PrivateQueueConcurrencyType)
childContext.parentContext = mainContext
Another approach would be to have both the main and child contexts use the same persistentStoreCoordinator like so:
let mainContext = NSManagedObjectContext( concurrencyType: .MainQueueConcurrencyType)
let childContext = NSManagedObjectContext( concurrencyType: .PrivateQueueConcurrencyType)
childContext.persistentStoreCoordinator = mainContext.persistentStoreCoordinator
Since we'll need to use performBlock on the childContext and later save or execute fetch or anything on the mainContext, what would be the difference between these two approaches?
I read on Florian Kugler's blog that the former approach processes on the Main Thread (which I tried and it didn't) and the latter is the preferred way. But every other site that I've looked seem to prefer former parent/child context.
To make things more confusing, on RayWenderLich's CoreData book (chapter 10 for references), they've used both of the approaches without explaining why.
Ideally there are 3 simple rule to achieve concurrency in single persistentStore core data application.
only one Managed Object context (MOC) should attached with persistentStoreCoordinator is basic rule to avoid MOC lock, unlock on CURD operation.
Every MOC should attached with one thread like Main thread MOC, background thread MOC.
You can not pass the managed object from one MOC(thread) to other by MOC(thread), in this case just pass ObjectID.
To achieve all three rule, apple introduce parent- child MOC approach. There are so many combination on various post,
But each stack (parent- child MOC approach) is highly depend on the application data usability.
I have implemented as my main MOC contexts use persistentStoreCoordinator, And created background thread child MOC for data synchronisation,
and more local child MOC for each View controller for creating new records screen. this will work well for me and tested with 3500 records insertion on DB.
The benefit doing this I am getting the updated server sync data through my main MOC.
I know few critics for approach, that I will block the main thread, But you can utilise batch update, delete, asynchronous fetch request to minimise it.

Core Data Concurrency with parent/child relationship and NSInvocationOperation done right?

I've been using Core Data to store iOS app data in a SQLite DB. Data is also synchronized to a central server during login or after n minutes have passed and as the app grew, synchronization got slower and needed a background process to prevent freezing of the main thread.
For this I rewrote my Synchronization class to use NSInvocationOperation class for each process in the synchronization method. I'm using dependencies as the data being imported/synchronized has several dependent relationships.
Then I add all operations into an NSOperationQueue and let it run.
There are two additional important elements:
When I generate the operations to execute I also create a new UUID NSString, which represents a synchronization ID. This is used to track which Entities in Core Data have been synchronized, therefore, every Entity has an attribute that stores this synchronization ID. It is only used during the synchronization process and is accesses by each method passed to NSInvocationOperation.
The import and synchronization methods are the actual methods that access Core Data. Inside each of those methods I create a new instance of NSManagedObjectContext as follows:
PHAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateContext setParentContext:[appDelegate mainManagedObjectContext]];
using NSPrivateQueueConcurrencyType as opposed to NSMainQueueConcurrencyType which is used in the AppDelegate for the main MOC. Then I create the parent/child relationship using the MOC from the main thread and that's that. When saving the child MOC, I understand that changes propagate back to the main MOC and to the store (SQLite).
I'm fairly new to concurrency in iOS and used this to guide me: http://code.tutsplus.com/tutorials/core-data-from-scratch-concurrency--cms-22131 (see Strategy #2). Different from this article and many other sources that I've seen, I do not subclass NSOperation. Instead, my approach uses NSInvocationOperation in order to pass each method that I want to run concurrently.
My question: Can you take a look at my code and see if you find something that I'm doing wrong or do you think that this will work? So far, I've had no problems running the code. But concurrency is tough to debug, so before we ship this code, I'd like to have more experienced eyes on this.
Thanks in advance!
Synchronization class code on pastebin.com: http://pastebin.com/CUzWw4Tv
(not everything is in the code paste, but it should be enough to evaluate the process, let me know if you need more)

Strange parent / child NSManagedObjectContext phenomenon

I have created two context like this:
// create writer MOC
_privateWriterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateWriterContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
// create main thread MOC
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.parentContext = _privateWriterContext;
I have a NSFetchResultedController initiated with _managedObjectContext.
I know it is strange, but I am adding a record to the parent to _privateWriterContext, I am saving it.
What is surprising is that child context and so FRC gets notified about this event. Why? I have not reset-ed child, or anything else. I thought they are independent entities as long as child context will not get saved.
In #pteofil article I found this line:
When a change is made in a context, but not saved, it is visible to all of its’ descendants but not to its’ ancestors.
.. it is pushed to the persistent store (via the persistent store coordinator) and becomes visible to all contexts connected to the store.
This is not supposed to happen. Adding an NSManagedObject ('record' ) to the parentContext, will not make the child aware of this object automatically. Only when you make the childContext execute a fetch, then it will fetch from the parentContext.
To figure out what is going on in your case, here are some suggestions:
figure out when a fetch is executed on the childContext (this is done by the fetchedRestultsController when you set it up. Check if that fetch happens before or after you add the managedObject to the parentContext).
set breakpoints in all four delegate callbacks of the fetchedResultsController to find out for which object it is calling the methods (and see if it is the object you just added to the parentContext).
make absolutely sure you know which context you are sending messages too.
I have a used a similar approach, but different: the childContext is the context is used to parse in new data (on a private queue), and when this parsing is done, the chid calls save:. This will save the changes up to the parent, which is in my case the mainQueueContext. This call to save: will cause the mainQueueContext to receive all the newly parsed objects, and any fetchedResultsController using that mainQueueContext will then call it's delegate methods for the new/changed/updated/delete objects.
You could try inverting your child/parent relationship too and see if it works as described in the docs, just to find out whats going on.
I strongly recommend avoiding parent-child context setups. Our book goes into detail why they often lead to strange behaviour: https://www.objc.io/books/core-data/
Short story: They're not as independent as you might think.
Use multiple context that share a single persistent store coordinator if you need more than a single context.

Core Data crash (EXC_BAD_ACCESS) on synchronizing contexts in mergeChangesFromContextDidSaveNotification

I'm writing an iPad cookery application which is heavily based on CoreData.
In my app I have a screen for editing recipe with 2 types of information:
Lists with recipe category\subcategory and country
All other recipe-related information
Each of this list may be edited in popover. Changes of this list shall be persisted immediately (i.e. if user will add some recipe category to possible categories list, but cancels recipe creation, this category shall be available for all recipes).
It was descided to implement 2 separate NSManagedObjectContexts to handle each type of info: main context for recipes management and supporting context for lists.
All core data operations performed via MagicalRecord framework. Both of contexts having MagicalRecord's default context as parent. Each context observes changes in other one. All contexts are being created and used on main thread so it seems that this issue has nothing related to multithreading issues.
When I'm trying to create object in support context and persist changes in support context immediately after object's creation everything going OK. The problem comes when newly created object is being deleted right after creation - EXC_BAD_ACCES received.
However, the entity is being persisted correctly and on next launch it may be used and deleted without synchronization issues.
1 note: When object is being accessed from Main context by existingObjectWithID: method of NSManagedObjectContext it becomes possible to delete this object. However crash happens on main context's (parent context of both Main and Supporting contexts) saving then.
Here is my code:
Entity creation:
RecipeCategory* category = [RecipeCategory MR_createInContext:_supportingContext];
category.name = itemName;
[_supportingContext MR_saveToPersistentStoreAndWait];
Entity deletion:
[(RecipeCategory*)itemToRemove MR_deleteEntity];
[_supportingContext MR_saveToPersistentStoreAndWait];
Contexts creation and observing setup:
[_mainContext MR_stopObservingContext:_supportingContext];
[_supportingContext MR_stopObservingContext:_mainContext];
_mainContext = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
_supportingContext = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
[_mainContext MR_observeContextOnMainThread:_supportingContext];
[_supportingContext MR_observeContextOnMainThread:_mainContext];
Please advice what may cause this issue because now I'm confused even in which way shall I move to solve this. Changes management section in Core Data documentation gives nothing. Same results gave google.
Don't do that. A context watching another that is also watching the watcher...that's bad. You first need to understand the rules of nested contexts and how data flows from one to the other on save.
I your case, you may want to look for the MR_confinementContext method on NSMOC. This will create a context that uses the old thread confinement model. This may help you get around your thread crashes. But first, don't do the circular observing...data flows in

Resources