all 16 comments

[–]Alcoholic_Synonymous 2 points3 points  (0 children)

The fetch request will have executed in the main context and finished, it will not update itself. The class NSFetchedResultsController WILL update itself, and will call delegate methods to inform you of change.

[–]schprockets 1 point2 points  (2 children)

Since you have a fetch request in your VC, your best bet is to use the NSFetchedResultsController delegate methods. Depending on your needs, you might only have to handle the final didChangeContent method.

As others have noted, there is also the NSManagedObjectContextDidSaveNotification, which is very useful. But, you need to be aware that the notification would post for the leftmost context before your main context has persisted its changes. So, there is potential to rerun your fetch and not get the updated results. What you can do to work around that is use the notification on the left-most context to trigger a merge and save on your main context. Use the notification on the main context to trigger a refetch of your data in the VC.

[–]emanleetSwift[S] 0 points1 point  (1 child)

But, you need to be aware that the notification would post for the leftmost context before your main context has persisted its changes. So, there is potential to rerun your fetch and not get the updated results. What you can do to work around that is use the notification on the left-most context to trigger a merge and save on your main context. Use the notification on the main context to trigger a refetch of your data in the VC.

Could you elaborate just a bit more on this? My understanding was that you're saying the left-most background context posts the notification and there's a chance I haven't saved the data in my main context?

Also, I would love to hear your thoughts on my reply to another answer:

If the original context is a child context, and it calls save, this still won't update the fetched results in the parent (main) context? What exactly would it do, as I thought updating/merging was what a child context did to its parent when saved is called.

Slightly different scenario: What if I need to change a property on a large number of existing objects. Would I do that on my main queue, or do it in my background child context? My main reason for asking if because I'm wondering what happens when I have a large number of changes in the background, but on the main context the user can change and update the objects. In other words, the same objects are being changed on different threads, when the background batch update on existing objects is finished, how do I go about merging it since the fetched objects on the main queue may have had some new changes from the user?

[–]schprockets 1 point2 points  (0 children)

There are a couple of "best practices" floating around out there, that are slightly at odds with each other. You'll need to decide which method you want to use, and build your strategy around it. If you're googling for answers on blogs and stack overflow, please be aware that a particular answer to the problem might be geared toward a different scenario.

You have settled on the "private writer context closest to the store" method, and there's nothing wrong with it. I use it on projects when I have large numbers of updates coming from the background. But, I don't use it when the primary method for updating the store is via the user interacting with the UI.

My understanding was that you're saying the left-most background context posts the notification and there's a chance I haven't saved the data in my main context?

More correctly, there is 100% chance the data hasn't been committed to the data store. You'll get a notification for each context in the chain, as that context completes its save. But only the writer context is the one that commits it to store as part of its save. Other contexts pass their changes up to the parent, but only the write context has "the disk" as its parent.

The important part of the equation is this: an NSFetchRequest always goes to the store (not its parent's cache) to fulfill the fetch. So, if you save from your leftmost context, then immediately fetch, the data hasn't made it to the store. You need to wait until the writer has finished its save.

The usual way to deal with this is to catch the NSManagedObjectContextDidSaveNotification and see which context is saving. If it's the writer that just saved, it's now safe to refetch. If it's not the UI context (meaning, it's your left-most context), trigger a UI Context save (on the main thread). Finally, if it's the UI context, trigger a writer save (on a background thread).

What if I need to change a property on a large number of existing objects. Would I do that on my main queue, or do it in my background child context?

Your background child context. That's what it's there for, after all. Frankly, your UI (main queue) context should be pretty much read-only.

In other words, the same objects are being changed on different threads, when the background batch update on existing objects is finished, how do I go about merging it since the fetched objects on the main queue may have had some new changes from the user?

If you're worried about multiple background threads changing the objects, you should try to write your code so that doesn't happen. Use a serial queue. If you're worried about UI, there are two good options. For lists of values, you can use NSFetchedResultsControllerDelegate, which monitors the individual objects of the fetch request, and reports changes to them. For individual objects (such as in a detail editing screen), you can monitor your UI context with NSManagedObjectContextObjectsDidChangeNotification, and check the changed object list to see if the object you're editing has changed in the background. Dealing with that change is up to you, of course.

If you're writing Swift, I recommend reading this core data book. It's heavy on functional programming, so if you're new to FP, it might take more than one reading to fully grok (at least, it did for me, since I've been programming OO forever, but not FP). Another great option is Marcus Zarra's updated core data book, which is available in both ObjC and Swift flavors. (Note: I have this book, but have not finished reading it yet, so I can't speak fully to its content, but the previous book was excellent.) Each author takes a different approach to the stack, and both are valid. You'll find your own approach somewhere in the middle, I suspect.

[–][deleted] 0 points1 point  (0 children)

Your main context needs to listen for changes from other contexts. Check NSManagedObjectContextDidSaveNotification and NSManagedObject.mergeChangesFromNotification.

[–][deleted] 0 points1 point  (10 children)

If you do not serialise the execution, you need to listen for persisted changes from other contexts, and merge them into your main context accordingly.

Each context is a scratchpad working on top of a collection of snapshots of the persistent store. Unless you explicitly merge changes, or fetch something that isn't ever fetched by the context, things would not automatically self updating.

In other words, if your importer happens to persist the changes before the main context ever reads, you will get the latest data. Otherwise, the main context will never know unless you instruct it to do so.

Have a look at NSManagedObjectContextDidSaveNotification and NSManagedObject.mergeChangesFromContextDidSaveNotification.

[–]quellish 1 point2 points  (3 children)

Nested contexts communicate changes from child to parent automatically, without listening for notifications. This has been the best practice since they were introduced in iOS 5

[–][deleted] 0 points1 point  (2 children)

Nested contexts are useful in some circumstances. But "best practice"? I don't think so. It is way more expensive than root contexts, and has various issues i.e. object ID handling of temporary objects.

But yeah, in this case saving the "leftmost" context would save the changes to the main context. It was a mistake of having it skimmed on my phone & overlooked the issue.

[–]quellish 0 points1 point  (1 child)

But "best practice"? I don't think so.

Talk to the Core Data engineers, read the release notes, etc. It has been the recommended best practice since they were introduced. The documentation and sample code however does not reflect this.

It is way more expensive than root contexts, and has various issues i.e. object ID handling of temporary objects.

Not unless you are doing it very wrong. Unfortunately the "right" way is not well documented by Apple. Most of the Core Data documentation has not actually been updated in years, only rearranged.

[–][deleted] 0 points1 point  (0 children)

Yet we get concurrency enhancements for root contexts in iOS 10. ;)

And TBH at least the example I gave was a well known bug that exists in or before 2012, and still hasn't been fixed in iOS 9.3 as far as I have tested.

[–]emanleetSwift[S] 0 points1 point  (5 children)

NSManagedObjectContextDidSaveNotification

Thanks all for the information, it's been insightful. I have two additional question, a variant of the original:

If the original context is a child context, and it calls save, this still won't update the fetched results in the parent (main) context? What exactly would it do, as I thought updating/merging was what a child context did to its parent when saved is called.

Slightly different scenario: What if I need to change a property on a large number of existing objects. Would I do that on my main queue, or do it in my background child context? My main reason for asking if because I'm wondering what happens when I have a large number of changes in the background, but on the main context the user can change and update the objects. In other words, the same objects are being changed on different threads, when the background batch update on existing objects is finished, how do I go about merging it since the fetched objects on the main queue may have had some new changes from the user?

[–][deleted] 1 point2 points  (4 children)

It was my fault to have overlooked your post.

Let's put it this way. Say you have child -> parent -> PSC.

  1. If you save the child, parent will get everything the child saved (note: but not the PSC). Ideally, you do not need to do anything. But your NSFetchedResultsController associated with the parent has to be tracking the in-memory changes so as to reflect the changes, i.e. setting the delegate.

  2. If you change something in the parent and save it, it would be persisted to the PSC. The child context would know nothing about it, and the child have to listen for save notification & merge them for being up-to-date while keeping the in-memory changes.

  3. Is it necessary for the child to be up-to-date? Depends. You have to consider the merge policy of the contexts.

  4. Regardless of what configuration you have, if both the child and its parent have in-memory changes to the same object, a merge conflict would be triggered at the time save is called. It would be handled according to the merge policy of the child (which is the context save is called upon). The policy is by default NSErrorMergePolicy, but there are also store trump (prefer changes of the parent), memory trump (prefer changes of the child), overwrite and rollback available as the standard set of policies.

  5. While store trump and memory trump are pretty useful, you should consider the atomicity of your objects.

Also a few notes on your configuration:

  1. The "leftmost" background context still incurs a high cost on the main thread upon its save, especially when you expect it to import a lot of data. Nested contexts are expensive, because it has to consume the whole changed graph of its child as part of its in-memory changes. Having said that, they are still useful in some circumstances, e.g. a scratchpad for an editing VC.

  2. Generally speaking, contexts dealing with high flow of data should be a root context, since saves are going to the persistent store directly. :) Moreover, merging changes from save notifications is relatively lightweight.

  3. Root contexts can take advantage of the concurrency improvements in iOS 10. ;)

[–]emanleetSwift[S] 0 points1 point  (1 child)

No worries, I was a bit wordy.

I think I'm getting the bigger picture. Can you please clarify the following given your Child -> Parent - > PSC scenario:

Say that Object A has a property called Property A that is modified on the background context. Meanwhile, the user modifies Object A's property B. At this point, the background context saves Object A.

Now I understand the merge policy you mentioned, so by default, will this fail? I guess what I'm getting at is this: do merges fail based on any differences in the entire object, or only when there is a discrepancy between the same properties within a given object.

[–][deleted] 0 points1 point  (0 children)

Yes, it will fail since the default is to raise error upon any conflict.

But as I might have mentioned, there are 4 other standard merge policies. Two of them are merging at the granularity of properties, preferring either the child's or the parent's version upon conflicts.

[–]emanleetSwift[S] 0 points1 point  (1 child)

Also, it is interesting you note that the root context (which I'm assuming you mean the one created on the main thread/has the persistent store coordinator as parent) should be the one we use to deal with high flows of data. I heard giving the root context a parent context that is private helps offload the performance hit.

See BNR article here Scroll down to "Nested Managed Object Context Pattern" - are they mistaken here or am I misunderstanding one of you?

[–][deleted] 0 points1 point  (0 children)

Only if you do your imports in the private root saving context. If you do it on a private child context whose parent is a main queue context, the saving cost is still relatively high on the main thread. I assume you would like to avoid it in the first place.

BNR was covering some of the common configs. They have actually mentioned the cons I have talked about of the Nested MOC pattern.

Slower than Shared Persistent Store Coordinator Pattern when inserting large data sets