NSFetchedResultsController pitfall

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.

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in iPhone. Bookmark the permalink.

Comments are closed.