I've finally at least narrowed down this problem. I'm computing some aggregate functions (as in this example the sum) of expenditures. If I change some expenditures, this aggregate fetch doesn't refresh immediately but only after a while (probably after the changes have been saved to the database). I've found this part in the doc:
- (void)setIncludesPendingChanges:(BOOL)yesNo
As per the documentation
A value of
YES
is not supported in conjunction with the result typeNSDictionaryResultType
, including calculation of aggregate results (such as max and min). For dictionaries, the array returned from the fetch reflects the current state in the persistent store, and does not take into account any pending changes, insertions, or deletions in the context. If you need to take pending changes into account for some simple aggregations like max and min, you can instead use a normal fetch request, sorted on the attribute you want, with a fetch limit of 1.
Ok how can I still include pending changes? I'm using a NSFetchedResultsController
to display my data. And here is my aggregate function:
- (NSNumber *)getExpendituresAmountForCostPeriod:(CostPeriod)costPeriod
{
NSLog(@"getExpenditures_Start");
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Expenditures"];
[fetchRequest setResultType:NSDictionaryResultType];
NSDate *startDate = [NSDate startDateForCostPeriod:[self getBiggestCostPeriod]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"forSpendingCategory = %@ AND date >= %@", self, startDate];
//Define what we want
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath: @"amount"];
NSExpression *sumExpression = [NSExpression expressionForFunction: @"sum:"
arguments: [NSArray arrayWithObject:keyPathExpression]];
//Defining the result type (name etc.)
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: @"totalExpenditures"];
[expressionDescription setExpression: sumExpression];
[expressionDescription setExpressionResultType: NSDoubleAttributeType];
// Set the request's properties to fetch just the property represented by the expressions.
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
NSLog(@"%@", self.managedObjectContext);
// Execute the fetch.
NSError *error = nil;
NSArray *objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (objects == nil) {
return [NSNumber numberWithDouble:0];
} else {
if ([objects count] > 0) {
return [[objects objectAtIndex:0] valueForKey:@"totalExpenditures"];
} else {
return [NSNumber numberWithDouble:0];
}
}
}
EDIT:
*Is a loop through the NSSet
possible and fast enough?*
- (NSNumber *)getExpendituresAmountForCostPeriod:(CostPeriod)costPeriod
{
NSDate *startDate = [NSDate startDateForCostPeriod:[self getBiggestCostPeriod]];
double total = 0;
for(Expenditures *expenditure in self.hasExpenditures){
if(expenditure.date >= startDate){
total = total + [expenditure.amount doubleValue];
}
}
return [NSNumber numberWithDouble:total];
}
EDIT FINALLY WITH ANSWER Thx to all of you I've finally found the problem in the loop. This works very fast and nice:
- (NSNumber *)getExpendituresAmountForCostPeriod:(CostPeriod)costPeriod
{
NSDate *startDate = [NSDate startDateForCostPeriod:[self getBiggestCostPeriod]];
double total = 0;
for(Expenditures *expenditure in self.hasExpenditures){
if([expenditure.date compare: startDate] == NSOrderedDescending){
total = total + [expenditure.amount doubleValue];
}
}
return [NSNumber numberWithDouble:total];
}
Called from controllerDidChangeContent.
That's enough for today.. :-)
I am not at all sure that using the predicate to filter out a subset is any faster from the primitive loop suggested beforehand.
It is a more concise and beautiful code, but by no means faster. Here are a few reasons (overheads) I can see immediately.
In the loop version --- there is no new allocation, no retaining nor releasing of anything, and just one pass over the self.expenditures set.
All in all, my point is, the second implementation will NEED to do AT LEAST the contents of that loop anyway, plus some more overheads.
And a last point: for id in collection can run concurrently using GCD on several items, hence it is quite fast.
I think you should at least try to match these alternatives via extensive performance test.
Your solution is OK, but you can still speed things up and produce shorter code by first shortening the set and then avoiding the loop by taking advantage of KVC: