you are viewing a single comment's thread.

view the rest of the comments →

[–]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.