聚合函数上的 iOS FetchRequest:如何包含待处理的更改?

Posted

技术标签:

【中文标题】聚合函数上的 iOS FetchRequest:如何包含待处理的更改?【英文标题】:iOS FetchRequest on aggregate functions: How to include pending changes? 【发布时间】:2013-12-30 16:24:33 【问题描述】:

我终于至少缩小了这个问题的范围。我正在计算一些支出的聚合函数(如本例中的总和)。如果我更改了一些支出,则此聚合获取不会立即刷新,而是会在一段时间后刷新(可能是在将更改保存到数据库之后)。我在文档中找到了这部分:

- (void)setIncludesPendingChanges:(BOOL)yesNo

根据文档

不支持将值 YES 与结果类型结合使用 NSDictionaryResultType,包括汇总结果的计算 (例如最大值和最小值)。对于字典,从返回的数组 fetch 反映持久存储中的当前状态,而不是 考虑到任何待处理的更改、插入或删除 语境。如果您需要考虑一些待处理的更改 像 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 调用。

今天就够了.. :-)

【问题讨论】:

恐怕你不能(因为NSDictionaryResultType暗示setIncludesPendingChanges:NO)。进行更改后,您必须明确保存上下文。 您可以随时保存上下文,唯一的问题是保存通常会使您的应用程序变慢。 - NSExpressionDescription 与显式循环相比的优势在于 NSExpressionDescription 在 SQLite 级别上执行,无需将所有对象提取到内存中(引发故障)。但我没有总体上更好的建议,您可能需要进行一些性能测试。 完全同意@MartinR。无论如何,如果您不想保存,循环是正确的选择。好吧,你应该按照马丁的建议做一些措施。同步意味着你需要等待保存结束,然后再调用你感兴趣的方法。所以,做一个保存,然后调用更新方法。 @MichiZH:是的。您可以设置启动参数-com.apple.CoreData.SQLDebug 1(或3)以在执行所有 SQLite 命令时获取有关它们的调试输出。 Objective-C 中没有运算符重载,请使用NSDatecompare: 方法(比较该类型的日期) 【参考方案1】:

您的解决方案还可以,但您仍然可以通过首先缩短集合然后利用 KVC 避免循环来加快速度并生成更短的代码:

NSSet *shortSet = [self.hasExpenditures filteredSetUsingPredicate:
  [NSPredicate predicateWithFormat:@"date > %@", startDate]];
NSNumber *total = [shortSet valueForKeyPath:@"@sum.amount"];

【讨论】:

太棒了,不知道你可以谓词一个集合。谢谢很多世界 还有一个问题,因为我检查了几个托管对象的类别,我总是获取最大位置或东西总和等。因为这些 valueForKeyPath 方法只是单行和使用已经获取的东西,它们一定比总是获取快很多吧? 在我的“主”视图控制器(我的第一个提取,主类别)中,我也在使用聚合函数。我可以在获取的结果集上以某种方式使用它们吗? 是的,您在所有方面都是正确的:使用情况、速度等。Collection Operators section of the Key Value Coding Guide 中有详细记录。【参考方案2】:

我完全不确定使用谓词过滤出一个子集是否比预先建议的原始循环更快。

这是一个更简洁和漂亮的代码,但绝不是更快。以下是我可以立即看到的一些原因(开销)。

    从文本格式创建和编译谓词需要时间和内存(多次分配) 再次使用谓词过滤 self.hasExpenditures 分配并启动一个新的 NSSet (shortSet) 并使用(保留的)对匹配支出(在日期范围内)的引用填充它。为此,它必须在循环中一一扫描 self.expenditures。 然后最后一次总计算,再次循环遍历对数量求和的子集,并分配最终的 NSNumber 对象。

在循环版本中 --- 没有新的分配,没有保留或释放任何东西,只有一次通过 self.expenditures 集。

总而言之,我的观点是,无论如何,第二个实现至少需要执行该循环的内容,再加上一些额外的开销。

最后一点:集合中的 for id 可以使用 GCD 在多个项目上同时运行,因此速度非常快。

我认为您至少应该尝试通过广泛的性能测试来匹配这些替代方案。

【讨论】:

以上是关于聚合函数上的 iOS FetchRequest:如何包含待处理的更改?的主要内容,如果未能解决你的问题,请参考以下文章

两个 ManagedObjectContexts 上的一个 FetchRequest

FetchRequest 上的 If 语句

在 iOS 中从 xcdatamodel 访问 FetchRequest

restkit 版本 0.20 上的 fetchRequest - 没有已知的类方法

iOS:在 fetchrequest 中仅获取核心数据中的简单信息

macOS Document App SwiftUI 项目上的 NSPersistentDocument FetchRequest 扭曲属性崩溃