NSFetchedResultsControllerDelegate controllerDidChangeContent
Requirement
Imagine a super simple API or rather something like RSS, that contains a list of items for example - active stores:
{
"stores": [
{
"name":"STORE 1",
"picture":"https://url.com/picture1",
},
{
"name":"STORE 2",
"picture":"https://url.com/picture2",
},
{
"name":"STORE 3",
"picture":"https://url.com/picture3",
}
]
}
This API does not give you any information if some new store opens up, or if any had been closed. So.. being the lazy me, I would implement it like this:
1.) Periodically download data from this API in background
2.) Disable all existing saved stores in app
3.) Iterate the ones, that are found in feed - and, if we had such store - overwrite meta data and enable them again
4.) Save background context, so that UI would reflect changes.
This way, I would:
- disable any stores that are no longer in feed,
- leave enabled any stores that are still in feed,
- by overwriting data every time (even if it’s the same data), I skip validation and make sure we always have fresh meta data.
Reality
Turns out, there is one major problem with my lazy implementation, that potentially could become a bad user experience.
I presumed, that if I:
1.) Disable STORE 1,
2.) Enable STORE 1,
3.) Save managed object context,
then STORE 1 would NOT be notified as a change. Because - it’s value has NOT changed. It was enabled before change, and it is enabled after saving.
But allas - THIS IS NOT THE CASE!
Some tests
Let’s test a few scenarios to find out how it actually works.
I set up a demo, that has some attributes and values added in Core Data.
Attribute | Value |
---|---|
title | “Description2” |
enabled | true |
On UI side - a table view on main thread shows this data in a list, with an NSFetchedResultsControllerDelegate hooked up, to receive any change notification.
Scenario | Steps | Change notification | |
---|---|---|---|
1 | Change value each time to a different value |
1.) title = “Adjusted from BG context” 2.) Save context 3.) title = “Description2” 4.) Save context |
YES |
2 | Change value to different and back to original |
1.) title = “Adjusted from BG context” 2.) title = “Description2” 3.) Save context |
YES |
3 | Same as test 2, but with bools | 1.) enabled = false 2.) enabled = true 3.) Save context |
YES |
4 | Set to the same value without changing it |
1.) enabled = true 2.) Save context |
YES |
Take away
Every time you set a NSManagedObject
value - to a new one or same as before - it will be notified, to any part of application, that listens to it.
Probably in most cases, this won’t make any difference, not even visible change if tableView.reloadData()
is used.
And in other cases, with a large dataset - overwritting is just much much faster, than checking/fetching/comparing every single entry.
But in that 1% case, when app is complicated, using relative small data set and developer aspires to make the best user experience, better take this into consideration.