为什么 NSFetchedResultsController 没有更新新数据?

发布于 2025-01-08 19:33:27 字数 7398 浏览 5 评论 0原文

我的核心数据模型有两个实体:具有一对多关系的AuthorBook(一位作者->多本书)。在主视图中,我显示一个书籍列表,其中每个单元格包含书籍名称和作者姓名。该视图还分为多个部分,每个部分的标题是作者姓名。 (请注意,为排序描述符和sectionNameKeyPath设置了“author.name”)

以下是代码(为了清晰起见,进行了简化):

- (NSFetchedResultsController *)fetchedResultsController {

    if (__fetchedResultsController != nil) {
        return __fetchedResultsController;
    }
    
    NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Book" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    
    NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"author.name" ascending:YES] autorelease];
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
    
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    NSFetchedResultsController *aFetchedResultsController = [[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"author.name" cacheName:nil] autorelease];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;
    
    NSError *error = nil;
    [self.fetchedResultsController performFetch:&error];
    
    return __fetchedResultsController;
}    

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

     Book* book = [self.fetchedResultsController objectAtIndexPath:indexPath];
     cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", book.name, book.author.name];
    return cell;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController*)controller {

    [self.tableView reloadData];
}

现在,如果用户更改作者姓名然后返回主视图,则将显示单元格和部分旧作者姓名。在互联网上搜索后,我发现以下代码修复了单元格中的旧作者姓名问题,但没有解决章节标题中的旧作者姓名问题:

- (void)saveAuthorName:(NSString *)newName {
    for (Book* book in author.books) {
        [book willChangeValueForKey:@"author"];
    }
     
    author.name = newName;

    for (Book* book in author.books) {
        [book didChangeValueForKey:@"author"];
    }
    
    // save changes
    NSError * error ;
    if( ![self.moc save:&error] ) {
        // Handle error
    } 
}

为什么 [self.fetchedResultsControllersections] 仍然包含旧作者姓名?请帮忙!

更新 #1

本节与 Marcus 的响应 #1 相关

嗯,还是有点模糊。您是说节数不正确吗?

部分的数量没有改变。 Sections 属性数组中的对象内容不正确。

根据您发布的代码,您只需从 NSFetchedResultsController 检索 NSManagedObject 实例。也许对于这是什么有些困惑?

在代码中,我从 NSFetchedResultsController 检索 NSManagedObject 实例,以便显示每个表格单元格的书名和作者姓名(当 cellForRowAtIndexPath被称为)。然而,根据我的理解,UITableView中每个部分的标题并不是取自NSManagedObject,而是取自实现NSFetchedResultsSectionInfo的_NSDefaultSectionInfo对象。 协议(当调用 titleForHeaderInSection 时)。

我通过编写用于调试的以下代码实现了这一点:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    id mySection = [[fetchedBooks sections] objectAtIndex:section];
    NSLog(@"%@", mySection)

    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

日志的结果是 <_NSDefaultSectionInfo: 0x8462b90>。 Sections 属性的 NSFetchedResultsController 文档显示:

/* Returns an array of objects that implement the NSFetchedResultsSectionInfo protocol.
   It's expected that developers use the returned array when implementing the following methods of the UITableViewDataSource protocol

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; 
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section;
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; 

*/

因此,如果我错了,请纠正我:_NSDefaultSectionInfo 不是 NSManagedObject 对吧?如果是这样,当作者 NSManagedObject 更改时,NSFetchedResultsController 如何检测 _NSDefaultSectionInfo 对象的更改?

因此这引出了几个问题:

您如何在另一个视图中更改作者的姓名?

更改作者姓名的代码写在上面的saveAuthorName中。应用程序中的流程如下:

  • 从主 UITableView 中,选择使用导航控制器打开新 Book 视图的书本单元。

  • 从书籍视图中,选择选择作者,这将使用导航控制器打开新的选择作者视图。 (所有作者都列在 UITableView 中)

  • 从“选择作者”视图中,选择使用导航控制器打开新“编辑作者”视图的任何作者。

  • 在“编辑作者”视图中更改作者姓名并保存,这会关闭视图并将上一个视图(选择作者)带入导航控制器堆栈中。

  • 现在可以选择不同的作者并对其进行编辑等等......直到关闭此视图。 (带来书籍视图)

  • 关闭书籍视图,进入显示所有书籍的主视图。

单元格中的作者姓名是旧的还是只是部分标题?

单元格已使用作者姓名完美更新(感谢 saveAuthorName 中调用的 willChangeValueForKeydidChangeValueForKey)。只有节标题是旧的。

您的委托方法是什么样的?

您能具体说明是哪一个吗?我在上面的代码部分中编写了所有看起来与我相关的委托方法。这包括:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • controllerDidChangeContent

还需要其他方法吗?

您确定您的 -[UITableViewDatasource tableView: titleForHeaderInSection:] 在您从编辑返回后会触发吗?

100%确定。 titleForHeaderInSection 带来旧值,并在保存更改后调用它。 (cellForRowAtIndexPath 也会在保存更改后调用,但会带来新值)

返回时会触发哪些 NSFetchedResultsControllerDelegate 方法?

如果您的意思是在保存时(意味着在调用 saveAuthorName 之后)调用以下方法:

  • controllerWillChangeContent: (不使用它,仅用于调试信息)

  • controller:didChangeObject:(不使用它,仅用于调试信息)

  • controllerDidChangeContent:

如果您的意思是返回主视图(意味着关闭图书视图)时调用以下方法:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • numberOfSectionsInTableView

  • numberOfRowsInSection

感谢您的帮助。谢谢!

更新#2

您是否正在实施 -controller: didChangeSection: atIndex: forChangeType:?

是的,我愿意,但更改作者姓名时不会被解雇。 NSFetchedResultsController 的当前配置如下:

  • 实体:书籍
  • 排序描述符:author.namesectionNameKeyPath
  • :author.name

更改书籍名称(而不是作者姓名)将触发 didChangeSection 事件,当 NSFetchedResultsController 配置如下时:

  • Entity: Book
  • Sort Descriptor: name
  • sectionNameKeyPath: name

这意味着委托已正确挂接到NSFetchedResultsController。

当更改作者姓名不足以用于 NSFetchedResultsController< 时,看起来会调用 [book willChangeValueForKey:@"author"][book didChangeValueForKey:@"author"] /code> 以监视部分更改。

My Core Data model has two entities: Author and Book with a To-Many relationship (one author->many books). In the main view I display a list of books where each cell contains book name and author name. The view is also divided into sections where each section title is the author name. (note that "author.name" is set for both sort descriptor and sectionNameKeyPath)

Here is the code (simplified for clarity):

- (NSFetchedResultsController *)fetchedResultsController {

    if (__fetchedResultsController != nil) {
        return __fetchedResultsController;
    }
    
    NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Book" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    
    NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"author.name" ascending:YES] autorelease];
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
    
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    NSFetchedResultsController *aFetchedResultsController = [[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"author.name" cacheName:nil] autorelease];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;
    
    NSError *error = nil;
    [self.fetchedResultsController performFetch:&error];
    
    return __fetchedResultsController;
}    

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

     Book* book = [self.fetchedResultsController objectAtIndexPath:indexPath];
     cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", book.name, book.author.name];
    return cell;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController*)controller {

    [self.tableView reloadData];
}

Now, if the user changes the author name and then goes back to main view, the cells and sections will display the old author name. After searching the Internet, I found the following code which fixes the old author name issue in the cells but not in section titles:

- (void)saveAuthorName:(NSString *)newName {
    for (Book* book in author.books) {
        [book willChangeValueForKey:@"author"];
    }
     
    author.name = newName;

    for (Book* book in author.books) {
        [book didChangeValueForKey:@"author"];
    }
    
    // save changes
    NSError * error ;
    if( ![self.moc save:&error] ) {
        // Handle error
    } 
}

Why is [self.fetchedResultsController sections] still contains old author names? Please help!

Update #1

This section relates to Response #1 of Marcus

Hmmm, still a little fuzzy. Are you saying the number of sections is incorrect?

The number of sections was not changed. The content of the objects in the Sections property array is incorrect.

Based on your code posted you are simply retrieving the NSManagedObject instances from the NSFetchedResultsController. Perhaps there is some confusion as to what that is?

In the code, I am retrieving the NSManagedObject instances from the NSFetchedResultsController in order to display book name and author name for each table cell (when cellForRowAtIndexPath is called). However, the headers of each section in UITableView are not taken from NSManagedObject to my understanding but are taken from _NSDefaultSectionInfo object which implements NSFetchedResultsSectionInfo protocol (when titleForHeaderInSection is called).

I realized this by the following code I wrote for debugging:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    id mySection = [[fetchedBooks sections] objectAtIndex:section];
    NSLog(@"%@", mySection)

    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

The result of the log was <_NSDefaultSectionInfo: 0x8462b90>.
NSFetchedResultsController documentation for Sections property shows:

/* Returns an array of objects that implement the NSFetchedResultsSectionInfo protocol.
   It's expected that developers use the returned array when implementing the following methods of the UITableViewDataSource protocol

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; 
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section;
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; 

*/

So please correct me if I am wrong: _NSDefaultSectionInfo is not an NSManagedObject right? If so, how can NSFetchedResultsController detect changes for _NSDefaultSectionInfo objects when Author NSManagedObject are changed?

So this leads to a couple of questions:

How are you changing the author's name in this other view?

The code for changing the author's name is written above in saveAuthorName. The flow in the App is as follows:

  • From the main UITableView, selecting the book cell which opens new Book view using navigation controller.

  • From the book view, choosing select author which opens new Select-Author view using navigation controller. (all authors are listed in a UITableView)

  • From Select-Author view, selecting any author which opens new Edit-Author view using navigation controller.

  • In the Edit-Author view changing author name and saving which closes view and bring the previous view (Select-Author) in navigation controller stack.

  • Now it's possible to select different author and editing it and so on.. until closing this view. (Brings Book view)

  • Closing Book view, brings to main view where all books are displayed.

Is the author name in the cell old or just the section header?

Cell is perfectly updated with author name (thanks to the willChangeValueForKey and didChangeValueForKey called in saveAuthorName). Only section header is old.

What do your delegate methods look like?

could you please specify which one exactly? I wrote all delegate methods that looks relevant to me in the above code section. This includes:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • controllerDidChangeContent

Any other method is required?

Are you certain that your -[UITableViewDatasource tableView: titleForHeaderInSection:] is firing after you return from the edit?

100% percent sure. titleForHeaderInSection brings old values and it is being called after changes were saved. (cellForRowAtIndexPath is also called after changes were saved but is bringing new values)

What NSFetchedResultsControllerDelegate methods are firing upon the return?

If you mean upon saving (means, after saveAuthorName is called) following methods being called:

  • controllerWillChangeContent: (not using it, just for debug info)

  • controller:didChangeObject: (not using it, just for debug info)

  • controllerDidChangeContent:

If you mean upon returning to main view (means, closing the Book view) following methods being called:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • numberOfSectionsInTableView

  • numberOfRowsInSection

I appreciate your help. Thanks!

Update #2

Are you implementing -controller: didChangeSection: atIndex: forChangeType:?

Yes I do, but is does not being fired when changing the author name. The current configuration for the NSFetchedResultsController is as follows:

  • Entity: Book
  • Sort Descriptor: author.name
  • sectionNameKeyPath: author.name

Changing a book name (rather than author name) will fire didChangeSection event when NSFetchedResultsController is configured as follows:

  • Entity: Book
  • Sort Descriptor: name
  • sectionNameKeyPath: name

Which means that the delegate is properly hooked to the NSFetchedResultsController.

It looks as calling [book willChangeValueForKey:@"author"] and [book didChangeValueForKey:@"author"] when changing author name is not enough for NSFetchedResultsController in order to monitor section changes.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

爱要勇敢去追 2025-01-15 19:33:27

通常,如果您正在为进行更改的 NSFetchedResultsControllerUIViewController 处理单个 NSManagedObjectContext,则不需要保存更改。

如果您有多个 NSManagedObjectContext,则这一点不适用。

假设您有一个 NSManagedObjectContext ,我会确保您在 NSFetchedResultsController 上设置了委托,并在委托方法中放置断点,以查看是否收到任何回调。

我还将使用调试器并打印出您正在使用的 NSManagedObject 的指针,并确保它们是相同的。如果不是,则表明 NSManagedObjectContext 存在问题。

回复 #1

嗯,还是有点模糊。您是说章节的数量不正确吗?

根据您发布的代码,您只需从 NSFetchedResultsController 检索 NSManagedObject 实例。也许对于那是什么有些困惑?

NSFetchedResultsController 只是一个具有一个或多个部分的容器。这些部分也只是一个包含一个或多个 NSManagedObject 实例的容器。这些是 相同 NSManagedObject 实例,您可以在应用程序中的其他任何位置访问这些实例(假设单个 NSManagedObjectContext 设计)。

因此,如果您在应用程序中任何地方更改NSManagedObject中的数据,它将在NSFetchedResultsController中更新,因为它是同一个对象,而不是复制但完全相同的对象。

因此,这引出了几个问题:

  1. 您如何在另一个视图中更改作者的姓名?
  2. 单元格中的作者姓名是旧的还是只是节标题?
  3. 您的委托方法是什么样的?
  4. 您确定您的 -[UITableViewDatasource tableView: titleForHeaderInSection:] 在您从编辑返回后会触发吗?
  5. 返回时会触发哪些 NSFetchedResultsControllerDelegate 方法?

响应 #2

您是否正在实施 -controller: didChangeSection: atIndex: forChangeType:?如果没有,请这样做并告诉我它是否会火。如果着火了,什么时候着火?在调用 -[UITableViewDatasource tableView: titleForHeaderInSection:] 之前还是之后?

回应 #3

这听起来像是 Apple 的 bug。

一些想法:

  1. 如果在作者被逗乐后执行 -performFetch: 会发生什么?我想知道这是否有效。
  2. 强烈建议您为此创建一个测试用例。然后,您可以将其提交给 Apple 以获取雷达(vital),我也可以进行测试,看看是否有一个干净的解决方案。

Generally you should not need to save the changes if you are dealing with a single NSManagedObjectContext for both the NSFetchedResultsController and the UIViewController that is making the changes.

That does not apply if you have more than one NSManagedObjectContext.

Assuming you have one NSManagedObjectContext, I would make sure you have the delegate set on the NSFetchedResultsController and put break points in the delegate methods to see if you are getting any callbacks at all.

I would also use the debugger and print out the pointers for the NSManagedObject(s) you are working with and make sure they are the same. If they are not then it would point to an issue with the NSManagedObjectContext.

Response #1

Hmmm, still a little fuzzy. Are you saying the number of sections is incorrect?

Based on your code posted you are simply retrieving the NSManagedObject instances from the NSFetchedResultsController. Perhaps there is some confusion as to what that is?

The NSFetchedResultsController is merely a container that has one or more sections. Those sections are also just a container that holds one or more NSManagedObject instances. Those are the same instances of NSManagedObject that you would access anywhere else in your application (assuming a single NSManagedObjectContext design).

Therefore if you change the data in a NSManagedObject anywhere in your application it will be updated in the NSFetchedResultsController because it is the same object, not a copy but the exact same object.

So this leads to a couple of questions:

  1. How are you changing the author's name in this other view?
  2. Is the author name in the cell old or just the section header? 
  3. What do your delegate methods look like?
  4. Are you certain that your -[UITableViewDatasource tableView: titleForHeaderInSection:] is firing after you return from the edit?
  5. What NSFetchedResultsControllerDelegate methods are firing upon the return?

Response #2

Are you implementing -controller: didChangeSection: atIndex: forChangeType:? If not, please do so and tell me if it fires. If it does fire, when does it fire? Before or after the call to -[UITableViewDatasource tableView: titleForHeaderInSection:]?

Response #3

This is starting to sound like an Apple bug.

A couple of thoughts:

  1. What happens if you do a -performFetch: after the author is tickled? I wonder if that would work.
  2. I strongly suggest that you create a test case for this. You can then submit it to Apple for a radar (vital) and I can play with the test also and see if there is a clean solution.
吃兔兔 2025-01-15 19:33:27

今天有了 Swift,一切都变得更加容易。

  1. 您在控制器中实现 NSFetchedResultsControllerDelegate
  2. 将控制器设置为 NSFetchedResultsController 的委托。
  3. 不要在 fetchedResultsController 上使用 performFetch,它会吞掉通常发送到委托的事件。
  4. 更改托管对象 ->应该调用委托方法。

Today with Swift everything is easier.

  1. You implement NSFetchedResultsControllerDelegate in your controller
  2. Set your controller as delegate of your NSFetchedResultsController.
  3. Do not use performFetch on your fetchedResultsController, it swallows up the event which normally goes to the delegate.
  4. Change a Managed object -> delegate methods should be called.
清风不识月 2025-01-15 19:33:27

为什么不提交保存更改的事务?

- (void)saveAuthorName:(NSString *)newName {
    for (Book* book in author.books) {
        [book willChangeValueForKey:@"author"];
    }

    author.name = newName;

    for (Book* book in author.books) {
        [book didChangeValueForKey:@"author"];
    }
    NSError * error ;
    if( ![self.moc save:&error] ) {
         ..... do something ..
    }
}

Why don't you commit the transactions saving changes?

- (void)saveAuthorName:(NSString *)newName {
    for (Book* book in author.books) {
        [book willChangeValueForKey:@"author"];
    }

    author.name = newName;

    for (Book* book in author.books) {
        [book didChangeValueForKey:@"author"];
    }
    NSError * error ;
    if( ![self.moc save:&error] ) {
         ..... do something ..
    }
}
初相遇 2025-01-15 19:33:27

我知道这是一个非常古老的问题,但在花了这么多时间研究、测试并尝试为自己解决同样的情况之后,我想分享我的发现。我相当确定这是一个 Apple 错误,并且我已针对 NSFetchedResultsSectionInfo 的 name 属性问题提交了反馈 FB7960282,该问题并不总是具有正确的值。

我的 Objc 有点生疏,所以我将在 Swift 中给出我的解决方法。

// This is a workaround for an apparent bug where the name property of an NSFetchedResultsSectionInfo
// element sometimes provides an incorrect name for the section.
private func name(for section: NSFetchedResultsSectionInfo) -> String {

    // The objects property of the NSFetchedResultsSectionInfo should always have at least one element
    let object = section.objects?.first as! NSObject
    // The name of the section should be the value of the Key Path of all objects in the array
    let name = object.value(forKeyPath: self.frc.sectionNameKeyPath!) as? String ?? ""
    return name
}

上面的函数假设 self.frc 是您的获取结果控制器。

顺便说一句,由于您的查询返回书籍,如果您想对作者的更改做出反应,您应该观察 NSManagedObjectContextDidSave 通知。

I know this is a very old question, but after spending so much time researching, testing and trying to resolve the same situation for myself, I want to share my findings. I am fairly certain that this is an Apple bug and I have filed Feedback FB7960282 for the issue of the name property of the NSFetchedResultsSectionInfo not always having the correct value.

My Objc is a little rusty, so I will give my the workaround in Swift.

// This is a workaround for an apparent bug where the name property of an NSFetchedResultsSectionInfo
// element sometimes provides an incorrect name for the section.
private func name(for section: NSFetchedResultsSectionInfo) -> String {

    // The objects property of the NSFetchedResultsSectionInfo should always have at least one element
    let object = section.objects?.first as! NSObject
    // The name of the section should be the value of the Key Path of all objects in the array
    let name = object.value(forKeyPath: self.frc.sectionNameKeyPath!) as? String ?? ""
    return name
}

The above function assumes that self.frc is your fetched results controller.

BTW, since your query is returning books, if you want to react to changes to authors, you should observe for NSManagedObjectContextDidSave notifications.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文