How do I Handle NSFetchedResultsControllerDelegate updates for custom results view - ios

I am currently working on a CoreData based iPhone app with a map view that will have its annotations generated from an NSFetchedResultsController. The idea of the map view is that it will show a number of saved locations for the user.
One of the advantages of using an NSFetchedResultsController is that I can set my map view as a delegate on NSFetchedResultsController and get notified of any changes made to the set of saved locations that happen on another device or on a website when the user is logged in.
I am currently having a bit of trouble getting my head around how to deal with a number of different kinds of updates that are sent to my NSFetchedResultsControllerDelegate implementation. The documentation: http://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html doesn't seem to tell you how these different updates should be handled as it seems more geared towards integrating with a UITableView, which does most of the work.
I am keeping an NSDictionary of my annotations that maps them to the NSIndexPath inside the result set. The issue is, for example, when I receive 10 move, 3 insert and 4 delete updates, what order should I process these in? Some of these indexPaths will have a number of conflicting indices and the order in which they are processed will have an affect on the actual annotations I need to move, insert or delete. If I perform all the move updates first, then the insert indices will cause the final order to be different to their final order if I inserted first.
Are there any existing small libraries/classes that will translate a set of indices prior to an update to a set of post update indices given those update messages? If not, can anyone explain how this stuff works so I can write my own?
Any help will be greatly appreciated!

The flow of the NSFetchedResultsControllerDelegate methods for one update cycle is like this:
controllerWillChangeContent: (called once)
controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: (called X times)
controllerDidChangeContent: (called once)
Every call to the didChangeObject method will be about just one object / indexPath. You should apply the changes in the order that you receive them via those calls to didChangeObject. That will keep them consistent.
If you are using sections, you could also have calls to controller:didChangeSection:atIndex:forChangeType: intermixed with the object changed calls.

Related

what's the best way to get notified when entity updates (when saved) in Core Data?

There's an article object that I am trying to keep monitoring in one ViewController. From my research there are couple of ways to achieve this but I am not sure which one is the most suitable one:
1, let the ViewController (or repository or viewModel if we are talking about clean architecture) be an observer to listen to the notification when core data saves.
2, in my Core Data abstract layer, add a completion block callback for when save() is called.
3, Use NSFetchedResultsController. I know this is designed for UITableView and UICollectionView, but I have seen people use this just to do the monitoring.
Among these 3 paths I am incline towards the third one but I am not 100% sure if that's the best practice since most people use it 1 to 1 on UITableView or UICollectionView.
If you want to observe when the object is changed, 1 and 2 will not necessarily help you.
You could do 3 - NSFetchedResultsController is very powerful - but you could also do the same thing that NSFetchedResultsController does internally, and it might be simpler:
You could register as an observer of NSManagedObjectContextObjectsDidChange.
It's posted once per pass through the run loop, if changes have been made, after the side-effects of such changes have been worked out. So it's safe to use if many changes are expected -- you'll only be notified once -- and double-ended relationships will be consistent.
It has a rich userInfo dictionary. Your task could be as simple as: check the userInfo's NSUpdatedObjectsKey and NSRefreshedObjectsKey for your object, and if it's there, refresh the views. Then check NSDeletedObjectsKey and NSInvalidatedObjectsKey too, and if your object is there, dismiss the view controller or return it to an "empty" state.

NSManagedObjectContext refreshObject causes duplicates in NSFetchedResultsController

I have an issue where if I make changes to a core data object, save then refreshing the object, causes my NSFetchedResultsController to show a duplicate object. I think I understand what's going on, but I'm looking for someone to confirm, and also to hopefully give some more detail as to why.
To explain in more dtail I have two entities, Fixture and Position. A Fixture has many Positions, and a Position belongs to only one Fixture. To reproduce the issue I do the following:
Fetch all Positions.
Modify some value (any one) on that objects Fixture. I.E foo.fixture.name = "foobar"
Save the context
Refresh objects by calling context.refreshAllObjects, or context.refreshObject(foo, mergeChanges: false/true).
I have a tableview using a fetched results controller which displays Fixures. After doing the above the tableview will display duplicates for each item (it doesn't matter if I use the delegate methods of the FRC to do the update or I just reload the tableview).
It appears what's happening is that the refresh invalidates the objects that the FRC knows about, while at the same time gets knowledge of another set of objects. If, as step #5, I call frc.performFetch() then the problem goes away.
Other things to note:
No matter how many times I run the code I only get two of each object (I'm using a random button to trigger it for testing).
init(entityName, context) is called on my Fixture subclass as soon as I access the Fixture property of my object during the next code run (i.e after refresh was called).
In my sample everything is taking place on the same context (though it happens with child contexts as well)
To give some more context as to how I got myself in this situation in the first place users can click on a fixture in the list and then ultimately narrow down on a single position a few screens later where they can perform actions that modify the fixture. There are other active areas of the application at this point that are listening to the NSManagedObjectContextDidSaveNotification and I want them to update their objects so they can display the correct data, which is why I was calling refresh.
I've dug around in the docs and I can't see anything that specifically explains my theory that updating the context causes NSFetchedResultsController to have invalid objects. Can anyone shed some light on this behavior?
First, you really should not be overriding -init... on a NSManagedObject. That is one of the methods that you are strongly discouraged from overriding and can very easily be the source of your issues.
Second, your entire UI should be using a single instance of the NSManagedObjectContext that is associated to the main queue and therefore there should only be ONE instance of any particular entity in your UI. If you have multiple contexts you are just making things more complicated for yourself. If you are using a single context keep in mind that no matter how many times you fetch an object against that context you will get the exact same pointer back.
The NSFetchedResultsController will never create objects, it only fetches and provides them for display. Therefore the NSFetchedResultsController is only reporting the fact that you have created this duplication somewhere else in your code.
Now some questions, do these duplicates get pushed down to disk?
Can you see them in the store file on disk?
When you re-launch your application are the duplicates still there?
When you put a print in your custom -init methods on the NSManagedObject; do they fire more than once?

Update NSFetchedResultsController fetch request predicate, refresh table with animations?

I cannot seem to find this laid out anywhere.
I have what is effectively a search box (though it's using a UITextView, not a search box/controller).
As the user types in words, I want to update the 'suggestions' in the table view below that are pulled from Core Data. These are displayed in a table below using the typical NSFRC setup.
After the user types (and with a delay of .5s) I update the fetch request predicate with their text, and call performFetch again.
What I really want is the new results to animate in, as you would normally see with NSFRC, of course leaving any existing matching rows in place. Currently the only way I get these to update is by calling reloadData on the table.
Is there a way to get what I'm after here?

Notification when UITableView changes

I often have a UITableViewController with an edit button, which I like to disable when there are no rows in the table. To keep this in sync, I enable/disable the button every time something happens that might update its dataSource - adding the first row, deleting the last row, in viewDidLoad, etc. Whenever I add some new functionality that can affect the contents of the table, I have to remember to incorporate this logic.
Is there some delegate of the UITableView that I can use to simplify this? A way to know whenever the table (or it's dataSource) is modified, where I can check the number of items in the dataSource and enable/disable accordingly.
Alternatively, any other approaches would be welcomed.
You are the data source. So you do know whenever the data source changes, if you care to know. In other words, the reason you're having this problem is that you're treating the model (in the model-view-controller architecture) as an alien being. Instead, treat the model as something of your own. Take charge of your model. For example, is the model an array? Then wrap it in a class of your own, to which all commands to change the array must be given. That way, it can emit a notification whenever it is told to change the array.
It is also possible under certain circumstances to use Key-Value Observing to get notified when something changes, and you could look into it, but with primitives like arrays and dictionaries it is possible that this will be more trouble than it's worth. Again, you're likely to be happier wrapping your model storage in your own class, whose observability via KVO you can manage yourself.

Multiple NSFetchedResultsController's with one UITableView?

Visually I have a UITableView with a UISegmentedControl to allow 'mode' selection which will change the table's sort order .
Using NSFetchedResultsController's I am thinking I should keep a separate controller for each different sort configuration then swap between which one is being displayed based on the currently selected 'mode'.
However I can see this will get tricky with 4 different NSFetchedResultsController all sending delegate messages to update the UITableView. To deal with this I am planning to use a switch() block in each of the relevant methods to ignore all but the currently 'active' NSFetchedResultsController.
Does this sound like the right approach or am I missing something obvious here?
-I can see potential for disaster if the user changes the 'mode' just as an update comes through (ie between controllerWillChangeContent: and controllerDidChangeContent:)
I've used a segment control to change sorting/grouping in several Core Data apps. In all of them, I've always just used a single NSFetchedResultsController, and re-queried the database when the segment changes.
As you correctly realized, it is a far simpler implementation that way, fewer chances of unexpected errors, and is also more scalable. E.g. what if I decide to add a new sorting/grouping segment based on customer feedback? How many NSFetchedResultsController's are you going to keep adding? At some point (5 or 6) it just becomes ridiculous.
Also graver's comment above that you could set all of their delegates, except the "active one" to nil won't scale (you would have to modify many lines of code to make all the other delegates nil, which will make maintaining the code hard.
So my suggestion is to go with the simpler implementation, which is a single NSFetchedResultsController, and recreate it every time the segment changes. If you're interested in caching, you could use a separate cache name for each segment. A unique cache name can be generated for each segment by concatenating something like:
[NSString stringWithFormat:#"Cache_%d", segment_index]
Isn't it more logical when the segmented control's selected index is changed, to change the sort descriptors and performFetch?
self.fetchedResultsController.fetchRequest.sortDescriptors = [NSArray ... ];

Resources