Question

I am trying to clean up some transient property recalculation events. In development I have just used a very broad stroke of updating on almost all changes. Now I am trying to determine the best practice, checking for relevant keys and updating as little as needed. My application objects are loaded with dozens of calculations using child and parent attributes where a single change can result in many cascading recalculations.

I thought I understood fairly well, referring to Apple Documentation here and I have experimented with patterns seen on some other StackOverflow posts such as this one by @Marcus S. Zarra. I see Marcus also posted an article on the subject here.

What I have currently is this:

#pragma mark _ worksheetTotal (transient)

+(NSSet *)keyPathsForValuesAffectingWorksheetTotal
{
    // local, non-transient, dependent keys here
    return [NSSet setWithObjects:@"dailySustenance", nil];
}

-(void)updateWorksheetTotal
{
    NSDecimalNumber *lineItemTotal = [self.items valueForKeyPath:@"@sum.lineHoursCost"];
    NSDecimalNumber *total = [lineItemTotal decimalNumberByAdding:self.dailySustenance];

    [self setWorksheetTotal:total];
}

-(void)setWorksheetTotal:(NSDecimalNumber *)newWorksheetTotal
{
    if ([self worksheetTotal] != newWorksheetTotal) {
        [self willChangeValueForKey:@"worksheetTotal"];
        [self setPrimitiveWorksheetTotal:newWorksheetTotal];
        [self didChangeValueForKey:@"worksheetTotal"];
    }
}

-(NSDecimalNumber *)worksheetTotal
{
    [self willAccessValueForKey:@"worksheetTotal"];
    NSDecimalNumber *result = [self primitiveWorksheetTotal];
    [self didAccessValueForKey:@"worksheetTotal"];

    return result;
}

I am thinking this is straight out of the documentation use case but my observers are not getting notified of the the changes to worksheetTotal.

When a change is made to a lineItem, that notification is received and responds updateWorksheetTotal is called here. I expect this would trigger another notification by the context that worksheetTotal has now changed, but it does not. I have tried numerous variations and combinations of code I have seen but nothing I do seems to make the NSManagedObjectContext consider my update to worksheetTotal to be a change worth reporting to observers.

What am I missing here?

Here is the relevant code for listening for the change in Parent object.

- (void) objectContextDidChange: (NSNotification *) notification
{
    NSSet *updatedObjects = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
    // Next look for changes to worksheet that may affect our calculated fields
    NSPredicate *worksheetPredicate = [NSPredicate predicateWithFormat:@"entity.name == %@ && estimate == %@", @"LaborWorksheet", self];
    NSPredicate *materialsPredicate = [NSPredicate predicateWithFormat:@"entity.name == %@ && estimate == %@", @"MaterialLineItems", self];
    NSPredicate *combinedPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:[NSArray arrayWithObjects:worksheetPredicate, materialsPredicate, nil]];
    NSSet *myWorksheets = [updatedObjects filteredSetUsingPredicate:combinedPredicate];
    // These are the relevant keys in this estimates labor and materials worksheet
    NSSet *relevantKeys = [NSSet setWithObjects:@"worksheetTotal", @"totalEquipmentcost", nil];
    BOOL clearCache = NO;
    if (myWorksheets.count != 0) {
        for (NSManagedObject *worksheet in myWorksheets) {
            NSLog(@"got some");
            // Here is where it fails, worksheetTotal is not in changedValues.allKeys. It is an empty set.
            // Not sure how that could be when I got it from updatedObjects set of userInfo. How could it have no changed values?
            NSSet *changedKeys = [NSSet setWithArray:worksheet.changedValues.allKeys];
            if ([changedKeys intersectsSet:relevantKeys]) {
                clearCache = YES;
                NSLog(@"found some, set to clear cache");
                // no need to continue checking
                break;
            }
        }
    }

    if (clearCache) {
      // I would update dependent keys here but it is never reached

    }
 }
Was it helpful?

Solution

The documentation for the changedValues method says:

This method only reports changes to properties that are defined as persistent properties of the receiver, not changes to transient properties or custom instance variables. This method does not unnecessarily fire relationship faults.

Since your property is transient, it will not appear there. I would suggest using KVO to monitor the changes in these properties.

OTHER TIPS

You are returning before didAccessValueForKey. Store to a variable and return it after didAccessValueForKey like

-(NSDecimalNumber *)worksheetTotal
{
    [self willAccessValueForKey:@"worksheetTotal"];
    NSDecimalNumber *yourVariable = [self primitiveWorksheetTotal];
    [self didAccessValueForKey:@"worksheetTotal"];
    return yourVariable;
}

I hope it works.

Edit:

In

-(void)setWorksheetTotal:(NSDecimalNumber *)newWorksheetTotal

consider changing

if ([self worksheetTotal] != newWorksheetTotal) {

to

if (worksheetTotal != newWorksheetTotal) {
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top