views:

420

answers:

3

I have a Core Data iPhone app that displays Subscription entities where any of its items are not read. In other words, I construct a predicate like this:

[NSPredicate predicateWithFormat:@"ANY items.read == NO"]

While this works for the initial fetch, it doesn't affect Subscription entities when I modify an Item, so the NSFetchedResultsController never reevaluates the Subscription entities. What would be a better way of structuring this so that the Subscription entity will be updated whenever an item's read property is set?

I did try creating a property unreadCount on Subscription and using keyPathsForValuesAffectingUnreadCount to return a set containing items.read. I didn't expect this to work, and it didn't. I get an exception from _NSFaultingMutableSet telling me that the read key is not supported.

A: 

Sorry, I should have read the question closer.

I've worked around a very similar situation by setting the NSFetchedResultsController cache and then refreshing my UITableView in the viewDidAppear: method. I had a pretty small dataset and there was no noticeable performance penalty for frequently refreshing the data.

kubi
Yes, you will be notified if `Subscription` changes. But modifying an `Item` doesn't modify the `Subscription`, so the fetched results controller doesn't see any changes to notify the delegate about.
Alex
The only downside to totally reloading the table view is that you lose some state, e.g. the selection status. I'm sure that the reloading will be infrequent enough not to impose a performance penalty, but I don't want to give up that little bit of UI feedback.
Alex
+1  A: 

I see two solutions:

  1. When you modify an item have the fetched results controller perform the fetch again and reload the table view.
  2. Add a property to Subscription (such as your unreadCount or a boolean hasUnreadItems) and keep it properly updated. Use this property in your fetched results controller.

You can get the set of unread items for a Subscription named "aSubscription" like this:

NSPredicate *pred = [NSPredicate predicateWithFormat:@"read == NO"];
NSSet *unreadItems = [aSubscription.items filteredSetUsingPredicate:pred];
gerry3
I was hoping someone would swoop in and offer some brilliant method I hadn't thought of, but I suspect you're right, particularly with solution #2. I think I'll just make `unreadCount` a "real" property and update it whenever an item gets updated.
Alex
Just an update for you, I ended up adding properties to `Subscription` so that `NSFetchedResultsController` would only need to observe one entity. Curiously, though, unless I fault in the updated objects in `managedObjectContextObjectsDidChange:`, `NSFetchedResultsController` still doesn't see them. It's a bit of a kludge, and I'm concerned about performance implications of faulting in a bunch of objects individually, but it does work now.
Alex
Weird. How are you updating the new property?
gerry3
Are you adding the property to just a subclass or actually putting the property into the Core Data entity in the model? Sounds like you are just putting it in the subclass which, as you have seen, does not work very well. gerry3's suggestion was to add a property to the entity in the model itself and keep that updated.
Marcus S. Zarra
A: 

You've encountered a bug with NSFetchedResultsController where notifications are not received (or evaluated?) for the property changes of the related entity.

In this case, the read property of items is not being "watched" for changes.

Rather than re-structuring, you can workaround the problem by faking the change of the items in the relationship. i.e. When you change item.read, also pretend to change item.

My experience only goes as far as a one-to-one relationship. I'm not clear on how you would handle your one-to-many relationship, when you're only changing a single key of one of the many. This may work:

thisItem.read = [NSNumber numberWithBool:NO];
[thisItem.subscription willChangeValueForKey:@"items"];
[thisItem.subscription didChangeValueForKey:@"items"];

That should send a notification that a relationship (subscription.items) is changing on the subscriptions objects being watched by the NSFetchedResultsController.

I can provide a working example of a one-to-one relationship case that works perfectly.

ohhorob