草庐IT

objective-c - 更改托管对象属性不会触发 NSFetchedResultsController 更新 TableView

coder 2023-07-25 原文

我有一个带有谓词的fetchedResultsController,其中"isOpen == YES"

当调用 closeCurrentClockSet 时,我将该属性设置为 NO。因此,它不应再出现在我的 tableView 上。

出于某种原因,这没有发生。

有人可以帮我解决这个问题吗?

-(void)closeCurrentClockSet
{

    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"isOpen == YES"];

    NSArray *fetchedObjects =
        [self fetchRequestForEntity:@"ClockSet"
                      withPredicate:predicate
             inManagedObjectContext:[myAppDelegate managedObjectContext]];

    ClockSet *currentClockSet = (ClockSet *)fetchedObjects.lastObject;

    [currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];

}

--

我还有几个方法,使用完全相同的方法, 通过调用自定义的 fetchRequestForEntity:withPredicate:inManagedObjectContext 方法。

在这些方法中,当更改属性时,tableView 会正确更新! 但是上面的这个 (closeCurrentClockSet) 没有!我不明白为什么。

--

我的 fetchedResultsController 实现来自 Apple 的文档。

还有一个细节。如果我将我的应用程序发送到后台。关闭它并重新打开,tableView 显示已更新!

我已尽力在 stackOverflow 上关注之前的问题。没运气。我也 NSLogged 到骨头。 正在正确获取对象。这是正确的。 isOpen 属性 已正确更新为NO。但出于某种原因,我的 fetchedResultsController 没有更新 tableView。

我确实尝试了几个“锤子”解决方案,例如 reloadData 和调用 performFetch。但这没有用。或者使用它们是有意义的......

编辑:从头开始,它确实有效,在我的 resultsController 上执行 performFetch 后立即调用 reloadData,但使用 reloadData 正在敲定一个解决方案。另外,它会删除所有动画。我希望我的 Controller 自动更新我的 tableView。

谁能帮我解决这个问题?

非常感谢任何帮助!

谢谢,

努诺

编辑:

完整的实现。

fetchedResultsController 非常标准和直接。其他一切都来自 Apple 的文档

- (NSFetchedResultsController *)fetchedResultsController
{

    if (_fetchedResultsController) {
        return _fetchedResultsController;
    }

    NSManagedObjectContext * managedObjectContext = [myAppDelegate managedObjectContext];

    NSEntityDescription *entity  =
        [NSEntityDescription entityForName:@"ClockPair"
                    inManagedObjectContext:managedObjectContext];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setEntity:entity];

    NSString *predicate = [NSString stringWithFormat: @"clockSet.isOpen == YES"];
        [fetchRequest setPredicate: [NSPredicate predicateWithFormat:predicate]];

    NSSortDescriptor *sortDescriptor1 =
        [[NSSortDescriptor alloc] initWithKey:@"clockIn" ascending:NO];

    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];

        [fetchRequest setSortDescriptors:sortDescriptors];
        [fetchRequest setFetchBatchSize:20];

    NSFetchedResultsController *theFetchedResultsController =
        [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                            managedObjectContext:managedObjectContext
                                              sectionNameKeyPath:nil
                                                       cacheName:@"Root"];


    _fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;

    return _fetchedResultsController;

}

--

Apple 文档中的样板代码:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    [self.tableView beginUpdates];
}



- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:

            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];

            break;

        case NSFetchedResultsChangeDelete:

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeUpdate:

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];

            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeMove:

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationLeft];

            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];

            break;
    }
}



- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id )sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type
{

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:

            [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                     withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeDelete:

            [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                     withRowAnimation:UITableViewRowAnimationFade];

            break;
    }
}



- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    [self.tableView endUpdates];
}

第一次更新:

Tracking [managedObjectContext hasChanges] 确实返回 YES,这是应该的。但是 fetchedResultsController 没有更新 tableView

第二次更新

didChangeObject:atIndexPath: 没有被调用 对于这种特殊情况! 我还有 2 个方法,使用完全相同的代码,它们只是碰巧是不同的实体。他们工作得很好。感谢@Leonardo 指出这一点

第三次更新 这个方法遵循同样的规则。但确实有效。

- (void)clockOut
{
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"isOpen == %@", [NSNumber numberWithBool:YES]];

    NSArray * fetchedObjects =
        [self fetchRequestForEntity:@"ClockPair"
                      withPredicate:predicate
             inManagedObjectContext:[myAppDelegate managedObjectContext]];

    ClockPair *aClockPair = (ClockPair *)fetchedObjects.lastObject;

    aClockPair.clockOut = [NSDate date];
    aClockPair.isOpen   = [NSNumber numberWithBool:NO];


}

有人对我可能遗漏的内容有任何其他想法吗?

谢谢,

努诺

最佳答案

OK,我会解释你的问题,然后让你判断是不是FRC的bug。如果您认为这是一个错误,那么您真的应该向 Apple 提交错误报告。

你的获取结果 Controller 谓词是这样的:

NSString *predicate = [NSString stringWithFormat: @"clockSet.isOpen == YES"];

这是 bool 值的有效谓词。它将遵循 clockSet 实体的关系并获取其 isOpen 属性。如果是 YES,那么这些对象将被接受到对象数组中。

我认为我们到此为止都很好。

现在,如果您将 clockSet.isOpen 属性之一更改为 NO,那么您希望看到该对象从您的 TableView 中消失(即,它不应再匹配谓词,因此它应该从获取的对象数组中删除)。

所以,如果你有这个......

[currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];

然后,无论哪个顶级对象与 currentClockSet 有关系,都应该从您的 FRC 获取结果数组中“消失”。

但是,您并没有看到它消失。原因是FRC监控的对象没有变化。是的,谓词键路径发生了变化,但 FRC 包含实体 ClockPairClockSet 实体实际上发生了变化。

您可以观看通知飞来飞去,看看幕后发生了什么。

无论如何,FRC 将在您进行抓取时使用关键路径,但它不会监视对不在其实际抓取对象集中的对象的更改。

最简单的解决方法是为包含此关键路径对象的对象“设置”一个属性。

例如,我注意到 ClockPair 也有一个 isOpen 属性。如果你有逆向关系,那么你可以这样做......

currentClockSet.isOpen = NO;
currentClockSet.clockPair.isOpen = currentClockSet.clockPair.isOpen;

请注意,您实际上根本没有更改该值。然而,setter 被调用,这触发了 KVO,因此私有(private)的 DidChange 通知,然后告诉 FRC 对象改变了。因此,它会重新评估检查以查看是否应包含该对象,发现 keypath 值已更改,然后执行您期望的操作。

因此,如果您在 FRC 谓词中使用关键路径,如果您更改该值,则需要蠕动回到 FRC 数组中的所有对象并“弄脏它们”,以便这些对象位于传递有关对象更改的通知。这很丑陋,但可能比保存或更改您的提取请求并重新提取要好。

我知道你不相信我,所以继续尝试吧。请注意,要使其正常工作,您必须知道 FRC 对象数组中的哪些项目会受到更改的影响,并“戳”它们以使 FRC 注意到更改。

如前所述,另一种选择是保存上下文并重新获取值。如果您不想保存上下文,则可以使获取包含当前上下文中的更新,而无需从存储中刷新。

我发现,对 FRC 正在监视的对象进行伪造更改是完成对作为其他实体的关键路径的谓词进行重新评估的最佳方式。

好的,所以,这是否是错误还有待商榷。就我个人而言,我认为如果 FRC 要监控 key 路径,它应该一直监控,而不是像我们在这里看到的那样部分监控。

我希望这是有道理的,我鼓励您提交错误报告。

关于objective-c - 更改托管对象属性不会触发 NSFetchedResultsController 更新 TableView ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12370521/

有关objective-c - 更改托管对象属性不会触发 NSFetchedResultsController 更新 TableView的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  3. ruby-on-rails - Ruby on Rails 迁移,将表更改为 MyISAM - 2

    如何正确创建Rails迁移,以便将表更改为MySQL中的MyISAM?目前是InnoDB。运行原始执行语句会更改表,但它不会更新db/schema.rb,因此当在测试环境中重新创建表时,它会返回到InnoDB并且我的全文搜索失败。我如何着手更改/添加迁移,以便将现有表修改为MyISAM并更新schema.rb,以便我的数据库和相应的测试数据库得到相应更新? 最佳答案 我没有找到执行此操作的好方法。您可以像有人建议的那样更改您的schema.rb,然后运行:rakedb:schema:load,但是,这将覆盖您的数据。我的做法是(假设

  4. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  5. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  6. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  7. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  8. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  9. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  10. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

随机推荐