I’ve previously written about how neat I find Core Data’s NSFetchedResultController
(NSFRC). Today, however, I must offer (another) word of warning. The documentation states that, if you set an NSFRC’s delegate:
[T]he controller registers to receive change notifications from its managed object context. Any change in the context that affects the result set or section information is processed and the results are updated accordingly.
I’m not going to come right out and say that this is a dirty, stinking lie, but I will say that my experience suggests that, in some circumstances, the NSFRC behavior described in the documentation is at variance with reality. Fortunately, there are workarounds.
Predicates
In simple cases, the NSFRC does, in fact, work as advertised. The problem crops up when you set the predicate
in the NSFetchRequest
passed to the NSFRC’s initializer. If you set it to something simple (where “simple” means, approximately, “only referencing properties of the entities returned by the NSFRC, and not including any keypaths”) like this:
NSFetchRequest* request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"Quux" inManagedObjectContext:someManagedObjectContext];
// Add a predicate to the request
request.predicate = [NSPredicate predicateWithFormat:@"ANY foos == %@",someFoo];
// Continue setting up the NSFetchRequest and creating the NSFRC
then things work just hunky-dory. If you go to something a little more complex, like this:
NSFetchRequest* request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"Quux" inManagedObjectContext:someManagedObjectContext];
// Add a predicate to the request
request.predicate = [NSPredicate predicateWithFormat:@"ANY foos.bar == %@",someBar];
// Continue setting up the NSFetchRequest and creating the NSFRC
then, eh, not so much. The initial query works fine, and changes to the result set resulting from changes to Quux
entities are handled correctly, but changes to the result set arising from changes to other entities (e.g. Foo
) are not.
Example
For instance, assume that you have set up an NSFRC along the lines of the second example, and that you have two Quux
object in your graph (call them A
and B
), each of which has one Foo
object in its foos
set. Further assume that A's Foo
has a bar
of someBar
, while
B's Foo
has a bar
of nil
.
In this case, the NSFRC’s result set will contain a single Quux
object (A
). If you then set the bar
of A's Foo
to nil
, you would expect A
to be removed from the result set. If you set the bar
of B's Foo
to someBar
, you would expect B
to be added to the result set. Neither will happen.
(Again: Changes to Quux
objects will be handled properly: If you removed the Foo
object from A's foos
set, then A
would be removed from the result set.)
Workaround
A somewhat nasty (but apparently effective) workaround is to manually mark all the neighbors of certain entities as “dirty” when certain properties are changed; which entities/properties require this handling depend on what predicates you’re using, and the feasibility of this technique depends on what your object graph looks like. I’ve found it reasonable, however. In the example we’ve been looking at, you might define a setter in the Foo
entity that looks like this:
- (void)setBar:(Bar*)newBar
{
[self willChangeValueForKey:@"bar"];
[self setPrimitiveBar:newBar];
[self didChangeValueForKey:@"bar"];
// This is a hack s.t. a particular meetingResultsController will update properly
for (Quux* q in self.quuxes) [q markDirty];
}
This assumes that quuxes
is the inverse relationship of foos
, and that Quux
has a markDirty
method. This latter might be defined as:
- (void)markDirty
{
[self willChangeValueForKey:@"desc"];
[self didChangeValueForKey:@"desc"];
}
where desc
is some random property of Quux
.
Caveats and Remarks
I should mention that I’ve encountered these issues in the context of merging changes between NSManagedObjectContexts
(MOCs); it’s possible that these problems don’t appear if you stay within a single MOC. That’s not the way I’d bet, though.
I also want to say that I don’t think there’s anything wrong with the behavior of NSFRC; it seems entirely reasonable from a performance point of view. I just wish it were better documented.