NSFetchedResultsController在更新后看不到新的插入/删除提取的值 [英] NSFetchedResultsController doesn't see new inserts / removes fetched values after update

查看:115
本文介绍了NSFetchedResultsController在更新后看不到新的插入/删除提取的值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在阅读了几十个类似的问题后,我想从这句话开始:我设置了NSFetchedResultsController的委托,它没有用。。

After reading dozens of similar questions, I'd like to start with this statement: "I DID set the delegate of the NSFetchedResultsController, it didn't work.".

所以我有一个简单的TableViewController,其单元格用NSFetchedResultsController填充。这是FRC初始化代码:

So I have a simple TableViewController whose cells are filled with NSFetchedResultsController. Here's the FRC init code:

@property (strong, nonatomic) NSFetchedResultsController *frc;

...

- (NSFetchedResultsController *)frc
{
    if (!_frc)
    {
        NSError *error = nil;
        NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product];
        request.sortDescriptors = [NSArray arrayWithObjects:
                               [NSSortDescriptor sortDescriptorWithKey:@"product_group.product_group_name" ascending:YES],
                               [NSSortDescriptor sortDescriptorWithKey:f_product_name ascending:YES],
                               nil];
        _frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:[DTGGlobalSettings sharedInstance].moc sectionNameKeyPath:@"product_group.product_group_name" cacheName:nil];
        _frc.delegate = self;
        [_frc performFetch:&error];
        if (error)
        {
            NSLog(@"Error while fetching products: %@!", error.userInfo);
        }
    }

    return _frc;
}

我还使用一些调试标签实现了NSFetchedResultsController委托方法:

I also have NSFetchedResultsController delegate methods implemented with some debugging labels:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    NSLog(@"controllerWillChangeContent");
    [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:
        NSLog(@"didChangeObject - insert");
        [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeDelete:
        NSLog(@"didChangeObject - delete");
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeUpdate:
        NSLog(@"didChangeObject - update");
        [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
        break;

    case NSFetchedResultsChangeMove:
        NSLog(@"didChangeObject - move");
        [tableView deleteRowsAtIndexPaths:[NSArray
                                           arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        [tableView insertRowsAtIndexPaths:[NSArray
                                           arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;
}
}


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

switch(type) {

    case NSFetchedResultsChangeInsert:
        NSLog(@"didChangeSection - insert");
        [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeDelete:
        NSLog(@"didChangeSection - delete");
        [self.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.
NSLog(@"controllerDidChangeContent");
[self.tableView endUpdates];
}

在我的navigationBar中,我有刷新按钮,其功能是启动执行以下操作的产品目录方法:
1.从远程MySQL服务器获取产品
2.如果具有ID的产品不存在 - 创建它并填充所有字段
3.如果它存在,所有字段都根据获取的值更新

In my navigationBar I have the refresh button, its function is to fire up the product catalog method who does the following: 1. Fetches products from remote MySQL server 2. If the product with ID doesn't exists - creates it and fills all the fields 3. If it exists, all the fields are updated according to the fetched values

在第2行和第3行之后,商店被保存。
我对整个应用程序中的所有CoreData操作使用单个NSManagedObjectContext。

After row 2 and 3 the store is saved. I use single NSManagedObjectContext for all CoreData operations across the app.

当我第一次启动应用程序时,TVC是空的,因为没有产品尚未获得。当我按下Refresh时,将获取新产品并将其插入ManagedObjectContext,但NSManagedObjectContext不会看到/听到任何更改:不会调用任何委托方法,因此不会向TVC添加新行。
当我重新启动应用程序时,新产品就位于TVC中,没有任何问题。

When I start the app for the first time, TVC is empty, because there was no products fetched yet. When I press Refresh, the new products are fetched and inserted into ManagedObjectContext, but NSManagedObjectContext doesn't see/hear any changes: no delegate methods are called, so no new rows are added to TVC. When I restart the app the new products are in place fetched into TVC with no problem.

如果我有一些产品再次按下刷新按钮经过短暂的延迟,他们都消失了。一些调试显示,如果在更新实体的字段(实际值相同)并保存商店后发生。这次委托方法就像一个魅力和每个更新和保存的实体控制器:didChangeObject:atIndexPath:forChangeType:newIndexPath用forChangeType = NSFetchedResultsChangeDelete调用。

If I press refresh button once again when there are some products there, after short delay they all disappear. Some debugging showed me that if happens after updating the fields of the entities (actually values are the same) and saving the store. Delegate methods this time work like a charm and for every updated and saved entity controller:didChangeObject:atIndexPath:forChangeType:newIndexPath is called with forChangeType = NSFetchedResultsChangeDelete.

问题#1:为什么NSFetchedResultsController在没有app重启的情况下看不到插入的实体?
问题2:为什么NSFetchedResultsController将已经获取的实体标记为不存在(?)并在商店保存后将其从TVC中删除?

Question #1: why NSFetchedResultsController doesn't see inserted entities without app restart? Question #2: why NSFetchedResultsController marks already fetched entities as "non existing" (?) and removes them from TVC after store save?

我将不胜感激帮助和想法,上周与这个问题斗争;(

I would appreciate any help and ideas, being fighting with this issue for whole last week ;(

UPD1(对于Jody):
这里有一些我使用自定义类DTGGlobalSettings在应用程序中共享一些变量。这就是我初始化其sharedInstance属性的方式:

UPD1 (for Jody): Here are some details. I use custom class DTGGlobalSettings to share some vars across the app. That's how I init its sharedInstance property:

+(DTGGlobalSettings *)sharedInstance
{
    static DTGGlobalSettings *myInstance = nil;

    if (nil == myInstance)
    {
        myInstance = [[[self class] alloc] init];
        // set values here
        // try to acces MOC to init CoreData 
        [myInstance moc];        
        myInstance.baseURL = @"http://local.app/";
}
return myInstance;
}

对于初始CoreData堆栈,我使用了Apple documentatio中的一个示例n,因为出于某种原因,我在创建新项目时没有看到使用CoreData复选框。我确信我之前在Xcode中看过它,但现在我没有;((Xcode 4.4.1):

To init CoreData stack I used an example from Apple documentation, because for some reason I don't see the checkbox "Use CoreData" when creating a new project. I'm sure that I've seen it before in Xcode, but now I don't ;( (Xcode 4.4.1):

#pragma mark Core Data stack

- (NSManagedObjectContext *) moc {

if (_moc != nil) {
    return _moc;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
    _moc = [[NSManagedObjectContext alloc] init];
    [_moc setPersistentStoreCoordinator: coordinator];
}
return _moc;
}


- (NSManagedObjectModel *)managedObjectModel {

if (_managedObjectModel != nil) {
    return _managedObjectModel;
}
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];    
return _managedObjectModel;
}


- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

if (_persistentStoreCoordinator != nil) {
    return _persistentStoreCoordinator;
}
NSURL *storeUrl = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
storeUrl = [storeUrl URLByAppendingPathComponent:DOC_NAME];

NSError *error;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];

if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
    NSLog(@"Error adding a store to the coordinator: %@, %@", error, error.userInfo);
}        
return _persistentStoreCoordinator;
}

-(void)saveDataStore
{
    NSError *error;
    if (![self.moc save:&error]) NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}

DTGGlobalSettings的'moc'(ManagedObjectContext)属性随后在应用程序,我需要访问CoreData。

The 'moc' (ManagedObjectContext) property of DTGGlobalSettings is then used everywhere in the app where I need access to CoreData.

现在到第二部分,更新数据库。我从远程web服务器获取JSON格式的一些新实体,通过生成的字典并为请求结果中找到的每个实体调用createFromDictionary方法。这是代码:

Now to the second part, updating the database. I'm getting some new entities in JSON format from remote web-server, going through the resulting dictionary and calling createFromDictionary method for each entity found in request results. Here's the code:

+(Product *)createFromDictionary:(NSDictionary *)entityData inContext:(NSManagedObjectContext *)moc performUpdate:(BOOL)update
{
Product *result;
if (update)
{
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product];
    request.predicate = [NSPredicate predicateWithFormat:@"%K = %@", f_product_id, [entityData objectForKey:f_product_id]];
    result = [[DTGGlobalSettings sharedInstance].moc executeFetchRequest:request error:nil].lastObject;
    if (!result)
    {
        NSLog(@"Error updating Product ID %@! Cannot fetch entity.", [entityData objectForKey:f_product_id]);
        return nil;
    }
} else
{
    result = [NSEntityDescription insertNewObjectForEntityForName:e_product inManagedObjectContext:[DTGGlobalSettings sharedInstance].moc];
}

result.product_id = [[DTGGlobalSettings sharedInstance].numFormatter numberFromString:[entityData objectForKey:f_product_id]];
result.product_image = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[DTGGlobalSettings sharedInstance].baseURL stringByAppendingString:[entityData objectForKey:f_product_image]]]];
result.product_name = [entityData objectForKey:f_product_name];

return result;
}

当我用完所提取的实体时,我调用[[DTGGlobalSettings sharedInstance] saveDataStore ] (往上看)。此时,TVC中的现有行(如果有的话)消失了。如果我要添加一些新实体,在保存时没有任何反应,即TVC不更新其行。

When I run out of fethed entities, I call [[DTGGlobalSettings sharedInstance] saveDataStore] (see above). At this point the existing rows in TVC (if there were any) disappearing. In case if I was adding some new entities, at the point of save nothing happens, i.e. TVC doesn't update its rows.

推荐答案

<实际上,我认为你的第一个预编辑帖子有所有必要的信息。我只是没有看过它(我不喜欢继续向右滚动读取长行代码,有时只是不做 - 我的坏)。

Actually, I think your first pre-edit posting had all the necessary information. I just did not read it (I don't like having to keep scrolling right read long lines of code and sometimes just don't do it - my bad).

您的问题似乎与您的FRC排序描述符有关。

Your issue seems to be related to your FRC sort descriptors.

FRC不能很好地保持关系排序描述符的更新。

FRC does not deal well with keeping relationship sort descriptors updated very well.

有些人认为它是如何设计的。我认为这是一个错误。

Some have argued that it is how it's designed. I argue it is a bug.

请参阅此帖子更改托管对象属性不会触发NSFetchedResultsController更新表格视图。希望你会看到与你的情况有相似之处。

See this post Changing a managed object property doesn't trigger NSFetchedResultsController to update the table view for my take. Hopefully, you will see the similarities to your case.

这篇关于NSFetchedResultsController在更新后看不到新的插入/删除提取的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆