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"];

Я совсем не уверен, что использование предиката для фильтрации подмножества быстрее, чем предложенный ранее примитивный цикл.

Это более лаконичный и красивый код, но отнюдь не быстрее. Вот несколько причин (накладные расходы), которые я вижу сразу.

  1. Создание и компиляция предиката из текстового формата требует времени и памяти (несколько выделений)
  2. Фильтрация self.hasExpenditures с использованием предиката снова выделяет и инициирует новый NSSet (shortSet) и заполняет его (сохраняются) ссылками на соответствующие расходы (в диапазоне дат). Для этого он должен сканировать self.expenditures один за другим в цикле.
  3. Затем последние итоговые вычисления снова зацикливаются на подмножестве, суммируя сумму и выделяя конечный объект NSNumber.

В циклической версии --- нет нового выделения, сохранения или освобождения чего-либо, и только один проход по набору self.expenditures.

В общем, моя точка зрения такова, что вторая реализация ДОЛЖНА в любом случае ДОЛЖНО делать содержимое этого цикла, плюс некоторые дополнительные издержки.

И последний момент: идентификатор в коллекции может работать одновременно с использованием GCD для нескольких элементов, следовательно, это довольно быстро.

Я думаю, что вы должны хотя бы попытаться сопоставить эти альтернативы с помощью обширного теста производительности.

Другие вопросы по тегам