How Not To Fight NSFetchedResultsController and Core Data
Context is everything with Core Data. No pun intended.
Note: This post assumes some basic knowledge of Core Data.
What do you get when you use NSFetchedResultsController with Core Data? You get peace of mind that your table cell insertions, removals, and reloads are smooth, passive, and painless. Painless, that is, unless you try to do something as simple as updating a related object and expecting it to be reflected in the UI.
Precondition
The Apple docs provide the comforting notice that everything will be automated for you.
When you need to change a relationship, Core Data takes care of the object graph consistency maintenance for you, so you need to change only one end of a relationship. This feature applies to to-one, to-many, and many-to-many relationships.
When I update a relationship, the other end of the relationship reflects those changes as well. Sounds good, right?
It is good. I updated the “One” side of my One-To-Many relationship and some Caveman Debugging shows that the “Manys” reflect that change. According to the NSFetchedResultsController docs everything should automatically work.
Once the fetched results controller is initialized, you assign it a delegate. The delegate will notify the table view controller when any changes have occurred to the underlying data structure.
But it doesn’t always do that. In fact, when I changed some data on the relationship my NSFetchedResultsController didn’t update at all.
My predicate for the controller was supposed to check to make sure both the item I was basing my tableview cells off of and its group object (which was the basis for the table view sections) were visible. In theory, hiding the group would hide all the items in the group without having to iterate over them and setting their visibility individually.
NSPredicate(format: "visible = YES && group.visible = YES")
But whenever I changed a group’s visibility, nothing happened. I could delete the entire group, and the deletion cascade rule would take effect and all the items would disappear from the table view just like the docs said would happen. I could modify the data on the item and it would updated the labels in the table view — just like Apple told me it would. I could hide the individual items and it would update the table view — exactly how you’d expect it to.
So why wasn’t the setting the visibility of my group object reflected in the fetched results controller?
Postcondition
It took me forever to find the solution. Finally someone on Stack Overflow explained what was happening:
I will let you judge whether it is a bug in FRC or not… The reason is that the object monitored by the FRC did not change. Yes, the predicate key path changed, but the FRC holds entities of [Item] and a [Group] entity actually changed.
Suddenly it all made sense. I had to update the relationship’s key path for my item’s group reference after updating the group. I’d completely forgotten about key paths. The joys of Obj-C designs while coding in Swift. This stupid-looking line of code is what finally got it working:
item.group.visible = item.group.visible
Wow. This will be fun to explain in the next code review! 😂
So remember, when using a fetched results controller, Core Data does not take care of the object graph consistency maintenance for you. You do need to change both ends of a relationship. Maybe after Swift 3.0, Apple will update Core Data to be more Swifty the way Realm did.
By the way, if you’re working with NSFetchedResultsController, you should definitely save yourself some time and check out this post by Michael Gachet on solving some NSFetchedResultsController woes. He has some really helpful tips and observations for dealing with background and foreground contexts while using an FRC too.