February 20, 2020

NSFetchedResultsControllerDelegate controllerDidChangeContent

2 minutes read



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


Scenario 1: Change value each time to a different value

Scenario 2: Change value to different and back to original
Scenario 3: Same as test 2, but with bools
Scenario 4: Set to the same value without changing it

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.