iOS FetchRequest о агрегатных функциях: как включить ожидающие изменения?
Я наконец, по крайней мере, сузил эту проблему. Я вычисляю некоторые агрегатные функции (как в этом примере сумму) расходов. Если я изменю некоторые расходы, эта совокупная выборка обновится не сразу, а только через некоторое время (возможно, после того, как изменения были сохранены в базе данных). Я нашел эту часть в документе:
- (void)setIncludesPendingChanges:(BOOL)yesNo
Согласно документации
Значение
YES
не поддерживается в сочетании с типом результатаNSDictionaryResultType
, в том числе расчет совокупных результатов (например, макс и мин). Для словарей массив, возвращаемый из выборки, отражает текущее состояние в постоянном хранилище и не учитывает никаких ожидающих изменений, вставок или удалений в контексте. Если вам нужно учесть ожидающие изменения для некоторых простых агрегатов, таких как max и min, вы можете вместо этого использовать обычный запрос на выборку, отсортированный по требуемому атрибуту, с пределом выборки 1.
Хорошо, как я могу включить ожидающие изменения? Я использую NSFetchedResultsController
отображать мои данные. И вот моя агрегатная функция:
- (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];
}
}
}
РЕДАКТИРОВАТЬ: * Это цикл через NSSet
возможно и достаточно быстро? *
- (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];
}
РЕДАКТИРУЙТЕ В ПОЛНОМ ОТВЕТЕ Спасибо всем вам, я наконец нашел проблему в цикле. Это работает очень быстро и приятно:
- (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];
}
Вызывается из controllerDidChangeContent.
На сегодня хватит..:-)
2 ответа
Ваше решение в порядке, но вы все равно можете ускорить процесс и создать более короткий код, сначала сократив набор, а затем избегая цикла, используя преимущества KVC:
NSSet *shortSet = [self.hasExpenditures filteredSetUsingPredicate:
[NSPredicate predicateWithFormat:@"date > %@", startDate]];
NSNumber *total = [shortSet valueForKeyPath:@"@sum.amount"];
Я совсем не уверен, что использование предиката для фильтрации подмножества быстрее, чем предложенный ранее примитивный цикл.
Это более лаконичный и красивый код, но отнюдь не быстрее. Вот несколько причин (накладные расходы), которые я вижу сразу.
- Создание и компиляция предиката из текстового формата требует времени и памяти (несколько выделений)
- Фильтрация self.hasExpenditures с использованием предиката снова выделяет и инициирует новый NSSet (shortSet) и заполняет его (сохраняются) ссылками на соответствующие расходы (в диапазоне дат). Для этого он должен сканировать self.expenditures один за другим в цикле.
- Затем последние итоговые вычисления снова зацикливаются на подмножестве, суммируя сумму и выделяя конечный объект NSNumber.
В циклической версии --- нет нового выделения, сохранения или освобождения чего-либо, и только один проход по набору self.expenditures.
В общем, моя точка зрения такова, что вторая реализация ДОЛЖНА в любом случае ДОЛЖНО делать содержимое этого цикла, плюс некоторые дополнительные издержки.
И последний момент: идентификатор в коллекции может работать одновременно с использованием GCD для нескольких элементов, следовательно, это довольно быстро.
Я думаю, что вы должны хотя бы попытаться сопоставить эти альтернативы с помощью обширного теста производительности.