2
votes

End Goal: I have an object-graph made up of leaves, connected to other leaves. I need to traverse the object-graph and return all those leaves that are not wilted and that either 1) don't have sub-leaves or 2) All sub-leaves are wilted.

Situation: I have an NSFetchedResultsController and table view where I'd like to display the results.

What I've Tried: I started out trying to use an NSPredicate on the NSFetchRequest, but realized there was no way that I could see which could recursively run through a leaf's sub-leaves and all their sub-leaves, etc...

So I added an attribute to the Leaf object called "isFarthestNonWiltedLeaf" and created a custom get-accessor inside of a category on Leaf:

- (NSNumber*) isFarthestNonWiltedLeaf
{
    [self willAccessValueForKey:@"isFarthestNonWiltedLeaf"];
    NSNumber *returnValue = @([self.wilted boolValue] == NO && [[self allSubLeavesAreWilted] boolValue]);
    [self didAccessValueForKey:@"isFarthestNonWiltedLeaf"];

    return returnValue;
}

- (NSNumber*) allSubLeavesAreWilted
{
    for(Leaf *aLeaf in self.subLeaves)
    {
        if([aLeaf.wilted boolValue] == NO || ![[aLeaf allSubLeavesAreWilted] boolValue])
            return @NO;
    }
    return @YES;
}

Then, on the NSFetchedResultsController's fetch request, I set the following predicate:

[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"isFarthestNonWiltedLeaf == YES"]];

This works great the first time that I open the application and started adding leaves and sub-leaves in a different view. However, the next time that I opened the app, the custom accessors method were not accessed the first time that the table view appeared! I have to go to each leaf and check/uncheck its "wilted" status, which then has the NSFetchedResultsController refresh that single Leaf... at that point it does call the custom isFarthestNonWiltedLeaf accessor and the leaf correctly appears in the list. I'd have to do this for each leaf for the entire tableview to be properly updated.

So my question is... how do I get the NSFetchRequest / NSFetchedResultsController to use the custom get accessor of the Leaf object each time? Thank you.

1

1 Answers

0
votes

Well, I'm going to have to answer my own question... I played around a bit more and I figured out the issue, though I'm still not sure about the "why" of it.

I realized that there is a lot of caching going on in CoreData, so even though I had the cacheName set to nil on the NSFetchedResultsController, I felt like there was some issue there. Also, it appeared that the primitive attribute was being accessed without calling the the get accessor.

So the primitive needs to be saved, in other words. So I added the following lines in the get accessor right after the value is calculated:

[self setPrimitiveValue:returnValue forKey:@"isFarthestNonWiltedLeaf"];

At first I also added in the KVO notification code (willChangeValueForKey and didChangeValueForKey) but that made the app completely unresponsive, for some reason... it appears to be some cyclical referencing issue.

So the final code looks like this:

- (NSNumber*) isFarthestNonWiltedLeaf
{
    [self willAccessValueForKey:@"isFarthestNonWiltedLeaf"];
    NSNumber *returnValue = @([self.wilted boolValue] == NO && [[self allSubLeavesAreWilted] boolValue]);
    [self didAccessValueForKey:@"isFarthestNonWiltedLeaf"];

    [self setPrimitiveValue:returnValue forKey:@"isFarthestNonWiltedLeaf"];

    return returnValue;
}

- (NSNumber*) allSubLeavesAreWilted
{
    for(Leaf *aLeaf in self.subLeaves)
    {
        if([aLeaf.wilted boolValue] == NO || ![[aLeaf allSubLeavesAreWilted] boolValue])
            return @NO;
    }
    return @YES;
}

...with the NSPredicate as follows:

[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"isFarthestNonWiltedLeaf == YES"]];