为什么有些东西会立即更新,有些东西不会?

时间:2013-12-29 14:25:30

标签: ios uitableview core-data nsfetchedresultscontroller

我现在一直在努力工作几个小时和几天。视图控制器中的某些内容会在发生更改时立即更新,而某些内容则不会更新。举个例子,我会尽量复制尽可能多的代码供您理解。

我正在为我的公司构建一个预算应用程序。三个实体是:MainCategorySpendingCategoryExpenditures。支出类别有几种支出,主要类别有几种支出类别。

MainCategory< - >> SpendingCategory< - >> Expenditures

编辑和添加交易发生在TransactionViewController。作为模型,我有一个SpendingCategory。您可以从主类别视图控制器访问此视图控制器,然后在prepareForSegue方法中设置SpendingCategory

以下是TransactionViewController的必要内容(我省略了viewDidLoad,因为只设置了标签和格式):

#pragma mark Model initialization
- (void)setSpendingCategory:(SpendingCategory *)spendingCategory
{
    _spendingCategory = spendingCategory;
    [self setupFetchedResultsController];
    self.title = NSLocalizedString(@"Transactions", nil);
}

- (SpendingCategory *)spendingCategory
{
    return _spendingCategory;
}

- (void)setupFetchedResultsController
{
    NSDate *startDate = [NSDate startDateForCostPeriod:[self.spendingCategory getBiggestCostPeriod]];

    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Expenditures"];
    request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"date" ascending:NO]];
    request.predicate = [NSPredicate predicateWithFormat:@"forSpendingCategory = %@ AND date >=%@", self.spendingCategory, startDate];

    self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request
                                                                       managedObjectContext:self.spendingCategory.managedObjectContext
                                                                        sectionNameKeyPath:nil                                                                                      cacheName:nil];
}

    - (void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
        [self updateBudgetAndBudgetLeft];
        [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:NO];
    }

    - (void)updateBudgetAndBudgetLeft
    {
        self.budgetLabel.text = [NSString stringWithFormat:@"%@/%@",[self.spendingCategory convertCostToBiggestCostPeriodAsString], [self.spendingCategory getBiggestCostPeriodAsString]];

        double moneySpent = [[self.spendingCategory getExpendituresAmountForCostPeriod:[self.spendingCategory getBiggestCostPeriod]] doubleValue];
        double budget = [[self.spendingCategory convertCostToBiggestCostPeriod] doubleValue];
        //Money spent will be ADDED because negative values are already stored as negative
        double budgetLeftCalc = budget + moneySpent;

        if(budgetLeftCalc >= 0){
            NSNumber *budgetLeft = [NSNumber numberWithDouble:budgetLeftCalc];
            self.budgetLeftLabel.text = [NSString stringWithFormat:@"%@ %@", [budgetLeft getLocalizedCurrencyStringWithDigits:0], NSLocalizedString(@"left", nil)];
        } else {
            NSNumber *budgetLeft = [NSNumber numberWithDouble:-budgetLeftCalc];
            self.budgetLeftLabel.text = [NSString stringWithFormat:@"%@ %@", [budgetLeft getLocalizedCurrencyStringWithDigits:0], NSLocalizedString(@"over", nil)];
            self.budgetLeftLabel.textColor = [UIColor redColor];
        }

        if (moneySpent < 0) {
            self.progressBar.progress = -moneySpent/budget;
        } else {
            self.progressBar.progress = 0;
        }

    }

从数据库中获取每期成本的函数是:(这显然没有及时得到正确的结果):

- (NSNumber *)getExpendituresAmountForCostPeriod:(CostPeriod)costPeriod
{
    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]];


    // 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];
        }
    }
}

如果您点击某个交易,则会转到ExpenditureViewController,该Expenditure具有date对象*支出的模型。此模型有三个属性:amountdescriptionupdateBudgetAndBudgetLeft

如果您现在更改某些交易或插入新交易,则此TransactionViewController不会执行任何操作!奇怪的是,经过一段时间后确实如此。如果等待20秒并重新加载TransactionViewController,则会发生更改。然而,我NSFetchedResultsController中显示的交易完全准确!为什么?为什么会延迟?为什么我甚至需要做一个单独的函数updateBudgetAndBudgetLeft,因为我认为我的CoredDataViewController会自动执行它?!在整个核心数据丛林中我会错过什么?

编辑2:

完成- (void)performFetch { if (self.fetchedResultsController) { if (self.fetchedResultsController.fetchRequest.predicate) { if (self.debug) NSLog(@"[%@ %@] fetching %@ with predicate: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName, self.fetchedResultsController.fetchRequest.predicate); } else { if (self.debug) NSLog(@"[%@ %@] fetching all %@ (i.e., no predicate)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName); } NSError *error; [self.fetchedResultsController performFetch:&error]; if (error) NSLog(@"[%@ %@] %@ (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]); } else { if (self.debug) NSLog(@"[%@ %@] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); } [self.tableView reloadData]; } - (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc { NSFetchedResultsController *oldfrc = _fetchedResultsController; if (newfrc != oldfrc) { _fetchedResultsController = newfrc; newfrc.delegate = self; if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) { self.title = newfrc.fetchRequest.entity.name; } if (newfrc) { if (self.debug) NSLog(@"[%@ %@] %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? @"updated" : @"set"); [self performFetch]; } else { if (self.debug) NSLog(@"[%@ %@] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); [self.tableView reloadData]; } } } #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [[self.fetchedResultsController sections] count]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects]; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return [[[self.fetchedResultsController sections] objectAtIndex:section] name]; } - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index]; } - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { return [self.fetchedResultsController sectionIndexTitles]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { return nil; } #pragma mark - NSFetchedResultsControllerDelegate - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { if(!self.reordering){ if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext) { [self.tableView beginUpdates]; self.beganUpdates = YES; } } } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { if(!self.reordering){ if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext) { switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } } } } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { if(!self.reordering){ if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext) { switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeUpdate: [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeMove: [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; } } } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { if(!self.reordering){ if (self.beganUpdates) [self.tableView endUpdates]; } } - (void)endSuspensionOfUpdatesDueToContextChanges { _suspendAutomaticTrackingOfChangesInManagedObjectContext = NO; } - (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend { if (suspend) { _suspendAutomaticTrackingOfChangesInManagedObjectContext = YES; } else { [self performSelector:@selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0]; } } -(void)stopSuspendAutomaticTrackingOfChangesInManagedObjectContextWithDelay:(double)delay { //the delay is necessary in my app after row editing [self performSelector:@selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:delay]; } (因为我使用嵌入式表视图,tableView是一个属性)

pragma mark - 获取

{{1}}

1 个答案:

答案 0 :(得分:1)

我会尝试提供一些提示,但由于代码太多,因此很难遵循逻辑。

首先,你告诉updateBudgetAndBudgetLeft没有做任何事情,但我看不出这个方法在任何地方被调用。如果viewWillAppear是调用该方法的唯一位置,则重新计算数据是不够的。事实上,你可以改变东西,但控制器不应该从屏幕上消失......这只是我的假设。

第二,在

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    if(!self.reordering){
        if (self.beganUpdates) [self.tableView endUpdates];
    }
}

你错过了检查self.suspendAutomaticTrackingOfChangesInManagedObjectContext bool值。

第三,为了解决问题,我建议忽略CoreDataViewController。这个类,我认为它被用作基类,可能会引入难以调试的问题。特别是对于核心数据新手。所以,暂时忘掉它。

基于这些假设,我要说的是应该明确调用updateBudgetAndBudgetLeft。当您使用NSFetchedResultsController时,它会跟踪您设置的实体(可以在NSFetchedResultsController pitfall中找到有趣的解释)。我想在这种情况下Expenditures,但没有人会调用update…方法。 例如,我的建议是在- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller中调用该方法。在那里,您确定已完成更改以进行跟踪。

修改1

  你错过了检查   self.suspendAutomaticTrackingOfChangesInManagedObjectContext bool   值。你在这里是什么意思?

如果您暂停跟踪更改,则不应调用endUpdates方法。如果我记得很清楚,斯坦福代码也会做类似的事情。检查一下。

  

所以你的建议是覆盖这个controllerDidChangeContent   在每个视图控制器中(它是子类化CoreDataViewController)   并在那里调用updateBudgetAndBudgetLeft?

是。这可能是一种选择。同时调用super也应该有效,但在这种情况下,即使您不想跟踪更改,也需要更新。无论如何,我会在没有基类的情况下工作。

  

为什么在viewWillAppear中调用此方法是不够的?我的意思是   每次视图可见时都应该调用它?

这取决于您重新计算数据的时间。如果您执行它并且控制器保持可见,则不会调用update…

修改2

根据文档并基于此新问题iOS FetchRequest on aggregate functions: How to include pending changes?,如果update…方法与NSDictionaryResultType类型一起使用,则必须先执行save。之后,可以正常调用它。换句话说,每次修改Expenditures项时,都应该save后跟update…。显然,节省太多会打击磁盘,因此可能导致应用程序变慢,但在这里你需要测量。否则,您可以在内存级别执行聚合。要做到这一点,只需过滤正确的实体即可。这可以在controllerDidChangeContentviewWillAppear轻松完成。