NSFetchedResultsController在更新为非获取的NSManagedObject之后不调用controllerDidChangeContent:

时间:2021-02-26 00:08:38
  1. I populate and save: an initial NSManagedObjectContext
  2. 我填充并保存:一个初始的NSManagedObjectContext
  3. setup an NSFetchedResultsController with a different NSManagedObjectContext, which filters on a boolean "show" attribute.
  4. 使用不同的NSManagedObjectContext设置NSFetchedResultsController,它过滤布尔“show”属性。
  5. Finally update "show" on yet another NSManagedObjectContext and save:.
  6. 最后在另一个NSManagedObjectContext上更新“show”并保存:

I expect that this should cause my NSFetchedResultsController to call NSFetchedResultsControllerDelegate's controllerDidChangeContent:. I never get that call. NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext's accepted answer indicates that in addition to controllerDidChangeContent:, I should get an NSManagedObjectContextObjectsDidChangeNotification, but I don't receive that either.

我希望这会导致我的NSFetchedResultsController调用NSFetchedResultsControllerDelegate的controllerDidChangeContent:。我从来没有接到那个电话。带有谓词的NSFetchedResultsController忽略从不同NSManagedObjectContext接受的答案中合并的更改表明除了controllerDidChangeContent:之外,我应该得到NSManagedObjectContextObjectsDidChangeNotification,但我也没有收到。

A complete code example is included below and on github. I've filed a radar with Apple.

完整的代码示例包含在下面和github上。我已经向Apple提交了雷达。

@interface HJBFoo : NSManagedObject

@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSNumber *show;

@end

@interface HJBAppDelegate () <NSFetchedResultsControllerDelegate>

@property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, strong) NSManagedObjectContext *initialManagedObjectContext;
@property (nonatomic, strong) NSManagedObjectContext *fetchedResultsControllerManagedObjectContext;
@property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;

@end

@implementation HJBAppDelegate

#pragma mark - UIApplicationDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    self.window.rootViewController = [UIViewController new];

    NSAttributeDescription *nameAttributeDescription = [NSAttributeDescription new];
    [nameAttributeDescription setAttributeType:NSStringAttributeType];
    [nameAttributeDescription setIndexed:NO];
    [nameAttributeDescription setOptional:NO];
    [nameAttributeDescription setName:@"name"];

    NSAttributeDescription *showAttributeDescription = [NSAttributeDescription new];
    [showAttributeDescription setAttributeType:NSBooleanAttributeType];
    [showAttributeDescription setIndexed:YES];
    [showAttributeDescription setOptional:NO];
    [showAttributeDescription setName:@"show"];

    NSEntityDescription *fooEntityDescription = [NSEntityDescription new];
    [fooEntityDescription setManagedObjectClassName:@"HJBFoo"];
    [fooEntityDescription setName:@"HJBFoo"];
    [fooEntityDescription setProperties:@[
     nameAttributeDescription,
     showAttributeDescription,
     ]];

    NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel new];
    [managedObjectModel setEntities:@[
     fooEntityDescription,
     ]];

    self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
    NSError *error = nil;
    if ([self.persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
                                                      configuration:nil
                                                                URL:nil
                                                            options:nil
                                                              error:&error]) {
        self.initialManagedObjectContext = [NSManagedObjectContext new];
        [self.initialManagedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];

        HJBFoo *foo1 = [NSEntityDescription insertNewObjectForEntityForName:@"HJBFoo"
                                                     inManagedObjectContext:self.initialManagedObjectContext];
        foo1.name = @"1";
        foo1.show = @YES;

        HJBFoo *foo2 = [NSEntityDescription insertNewObjectForEntityForName:@"HJBFoo"
                                                     inManagedObjectContext:self.initialManagedObjectContext];
        foo2.name = @"2";
        foo2.show = @NO;

        error = nil;
        if ([self.initialManagedObjectContext save:&error]) {
            NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"HJBFoo"];
            [fetchRequest setReturnsObjectsAsFaults:NO];

            error = nil;
            NSArray *initialFoos = [self.initialManagedObjectContext executeFetchRequest:fetchRequest
                                                                                   error:&error];
            if (initialFoos) {
                NSLog(@"Initial: %@", initialFoos);

                self.fetchedResultsControllerManagedObjectContext = [NSManagedObjectContext new];
                [self.fetchedResultsControllerManagedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];

                NSFetchRequest *shownFetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"HJBFoo"];
                [shownFetchRequest setPredicate:[NSPredicate predicateWithFormat:@"show == YES"]];
                [shownFetchRequest setSortDescriptors:@[
                 [NSSortDescriptor sortDescriptorWithKey:@"name"
                                               ascending:YES],
                 ]];

                self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:shownFetchRequest
                                                                                    managedObjectContext:self.fetchedResultsControllerManagedObjectContext
                                                                                      sectionNameKeyPath:nil
                                                                                               cacheName:nil];
                self.fetchedResultsController.delegate = self;
                error = nil;
                if ([self.fetchedResultsController performFetch:&error]) {
                    NSLog(@"Initial fetchedObjects: %@", [self.fetchedResultsController fetchedObjects]);

                    [[NSNotificationCenter defaultCenter] addObserver:self
                                                             selector:@selector(managedObjectContextDidSave:)
                                                                 name:NSManagedObjectContextDidSaveNotification
                                                               object:nil];
                    [[NSNotificationCenter defaultCenter] addObserver:self
                                                             selector:@selector(managedObjectContext2ObjectsDidChange:)
                                                                 name:NSManagedObjectContextObjectsDidChangeNotification
                                                               object:self.fetchedResultsControllerManagedObjectContext];

                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC),
                                   dispatch_get_main_queue(),
                                   ^(void){
                                       NSManagedObjectContext *managedObjectContext3 = [NSManagedObjectContext new];
                                       [managedObjectContext3 setPersistentStoreCoordinator:self.persistentStoreCoordinator];

                                       NSFetchRequest *foo2FetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"HJBFoo"];
                                       [foo2FetchRequest setFetchLimit:1];
                                       [foo2FetchRequest setPredicate:[NSPredicate predicateWithFormat:@"name == %@",
                                                                       @"2"]];
                                       NSError *editingError = nil;
                                       NSArray *editingFoos = [managedObjectContext3 executeFetchRequest:foo2FetchRequest
                                                                                                   error:&editingError];
                                       if (editingFoos) {
                                           HJBFoo *editingFoo2 = [editingFoos objectAtIndex:0];
                                           editingFoo2.show = @YES;

                                           editingError = nil;
                                           if ([managedObjectContext3 save:&editingError]) {
                                               NSLog(@"Save succeeded. Expected (in order) managedObjectContextDidSave, controllerDidChangeContent, managedObjectContext2ObjectsDidChange");
                                           } else {
                                               NSLog(@"Editing save failed: %@ %@", [error localizedDescription], [error userInfo]);
                                           }
                                       } else {
                                           NSLog(@"Editing fetch failed: %@ %@", [error localizedDescription], [error userInfo]);
                                       }

                                   });
                } else {
                    NSLog(@"Failed initial fetch: %@ %@", [error localizedDescription], [error userInfo]);
                }
            } else {
                NSLog(@"Failed to performFetch: %@ %@", [error localizedDescription], [error userInfo]);
            }
        } else {
            NSLog(@"Failed to save initial state: %@ %@", [error localizedDescription], [error userInfo]);
        }
    } else {
        NSLog(@"Failed to add persistent store: %@ %@", [error localizedDescription], [error userInfo]);
    }

    [self.window makeKeyAndVisible];
    return YES;
}

#pragma mark - NSFetchedResultsControllerDelegate

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    NSLog(@"controllerDidChangeContent: %@",
          [self.fetchedResultsController fetchedObjects]);
}

#pragma mark - notifications

- (void)managedObjectContextDidSave:(NSNotification *)notification {
    NSManagedObjectContext *managedObjectContext = [notification object];
    if (([managedObjectContext persistentStoreCoordinator] == self.persistentStoreCoordinator) &&
        (managedObjectContext != self.fetchedResultsControllerManagedObjectContext)) {
        NSLog(@"managedObjectContextDidSave: %@", notification);
        [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }
}

- (void)managedObjectContext2ObjectsDidChange:(NSNotification *)notification {
    NSLog(@"managedObjectContext2ObjectsDidChange: %@", notification);
}

@end

@implementation HJBFoo

@dynamic name;
@dynamic show;

@end

1 个解决方案

#1


17  

It seems to me that applying the fix/workaround from NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext solves your problem as well. Your managedObjectContextDidSave method would then look like this:

在我看来,应用NSFetchedResultsController的修复/变通方法与谓词忽略从不同NSManagedObjectContext合并的更改也解决了您的问题。您的managedObjectContextDidSave方法将如下所示:

- (void)managedObjectContextDidSave:(NSNotification *)notification {
    NSManagedObjectContext *managedObjectContext = [notification object];
    if (([managedObjectContext persistentStoreCoordinator] == self.persistentStoreCoordinator) &&
        (managedObjectContext != self.fetchedResultsControllerManagedObjectContext)) {
        NSLog(@"managedObjectContextDidSave: %@", notification);

        // Fix/workaround from https://*.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different/3927811#3927811
        for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
            [[self.fetchedResultsControllerManagedObjectContext objectWithID:[object objectID]] willAccessValueForKey:nil];
        }

        [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }
}

and the NSLog output looks as expected:

并且NSLog输出看起来像预期的那样:

Initial: (
    "<HJBFoo: 0xaa19670> (entity: HJBFoo; id: 0xaa1afd0 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p2> ; data: {\n    name = 1;\n    show = 1;\n})",
    "<HJBFoo: 0xaa1a200> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n    name = 2;\n    show = 0;\n})"
)
Initial fetchedObjects: (
    "<HJBFoo: 0x74613f0> (entity: HJBFoo; id: 0xaa1afd0 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p2> ; data: <fault>)"
)
managedObjectContextDidSave: NSConcreteNotification 0xaa1f480 {name = NSManagingContextDidSaveChangesNotification; object = <NSManagedObjectContext: 0xaa1ed90>; userInfo = {
    inserted = "{(\n)}";
    updated = "{(\n    <HJBFoo: 0xaa1f2d0> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n    name = 2;\n    show = 1;\n})\n)}";
}}
Save succeeded. Expected (in order) managedObjectContextDidSave, controllerDidChangeContent, managedObjectContext2ObjectsDidChange
controllerDidChangeContent: (
    "<HJBFoo: 0x74613f0> (entity: HJBFoo; id: 0xaa1afd0 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p2> ; data: {\n    name = 1;\n    show = 1;\n})",
    "<HJBFoo: 0xaa1f9c0> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n    name = 2;\n    show = 1;\n})"
)
managedObjectContext2ObjectsDidChange: NSConcreteNotification 0xaa1fbb0 {name = NSObjectsChangedInManagingContextNotification; object = <NSManagedObjectContext: 0x745fa50>; userInfo = {
    managedObjectContext = "<NSManagedObjectContext: 0x745fa50>";
    refreshed = "{(\n    <HJBFoo: 0xaa1f9c0> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n    name = 2;\n    show = 1;\n})\n)}";
}}

So the following things happen:

所以发生以下事情:

  • Changes are made in the "background" context managedObjectContext3 and saved.
  • 更改在“后台”上下文managedObjectContext3中进行并保存。
  • You receive a NSManagedObjectContextDidSaveNotification and managedObjectContextDidSave: is called. This notification contains the object that was updated in the background context.
  • 您收到NSManagedObjectContextDidSaveNotification和managedObjectContextDidSave:被调用。此通知包含在后台上下文中更新的对象。
  • Calling

    调用

    [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    

    now would not do anything, because the updated object has not been loaded into the fetchedResultsControllerManagedObjectContext or is a fault. In particular, this context would not post a NSManagedObjectContextObjectsDidChangeNotification and the fetched results controller would not update.

    现在不会做任何事情,因为更新的对象尚未加载到fetchedResultsControllerManagedObjectContext中或者是一个错误。特别是,此上下文不会发布NSManagedObjectContextObjectsDidChangeNotification,并且获取的结果控制器不会更新。

  • But calling willAccessValueForKey for the updated objects first forces the fetchedResultsControllerManagedObjectContext to load these objects and fire a fault.
  • 但是为更新的对象调用willAccessValueForKey会强制fetchedResultsControllerManagedObjectContext加载这些对象并触发故障。
  • Calling

    调用

    [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    

    after that actually updates the objects in the fetchedResultsControllerManagedObjectContext.

    之后实际更新fetchedResultsControllerManagedObjectContext中的对象。

  • Therefore a NSManagedObjectContextObjectsDidChangeNotification is posted and the fetched results controller calls the corresponding delegate functions.
  • 因此,将发布NSManagedObjectContextObjectsDidChangeNotification,并且获取的结果控制器将调用相应的委托函数。

#1


17  

It seems to me that applying the fix/workaround from NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext solves your problem as well. Your managedObjectContextDidSave method would then look like this:

在我看来,应用NSFetchedResultsController的修复/变通方法与谓词忽略从不同NSManagedObjectContext合并的更改也解决了您的问题。您的managedObjectContextDidSave方法将如下所示:

- (void)managedObjectContextDidSave:(NSNotification *)notification {
    NSManagedObjectContext *managedObjectContext = [notification object];
    if (([managedObjectContext persistentStoreCoordinator] == self.persistentStoreCoordinator) &&
        (managedObjectContext != self.fetchedResultsControllerManagedObjectContext)) {
        NSLog(@"managedObjectContextDidSave: %@", notification);

        // Fix/workaround from https://*.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different/3927811#3927811
        for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
            [[self.fetchedResultsControllerManagedObjectContext objectWithID:[object objectID]] willAccessValueForKey:nil];
        }

        [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }
}

and the NSLog output looks as expected:

并且NSLog输出看起来像预期的那样:

Initial: (
    "<HJBFoo: 0xaa19670> (entity: HJBFoo; id: 0xaa1afd0 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p2> ; data: {\n    name = 1;\n    show = 1;\n})",
    "<HJBFoo: 0xaa1a200> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n    name = 2;\n    show = 0;\n})"
)
Initial fetchedObjects: (
    "<HJBFoo: 0x74613f0> (entity: HJBFoo; id: 0xaa1afd0 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p2> ; data: <fault>)"
)
managedObjectContextDidSave: NSConcreteNotification 0xaa1f480 {name = NSManagingContextDidSaveChangesNotification; object = <NSManagedObjectContext: 0xaa1ed90>; userInfo = {
    inserted = "{(\n)}";
    updated = "{(\n    <HJBFoo: 0xaa1f2d0> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n    name = 2;\n    show = 1;\n})\n)}";
}}
Save succeeded. Expected (in order) managedObjectContextDidSave, controllerDidChangeContent, managedObjectContext2ObjectsDidChange
controllerDidChangeContent: (
    "<HJBFoo: 0x74613f0> (entity: HJBFoo; id: 0xaa1afd0 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p2> ; data: {\n    name = 1;\n    show = 1;\n})",
    "<HJBFoo: 0xaa1f9c0> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n    name = 2;\n    show = 1;\n})"
)
managedObjectContext2ObjectsDidChange: NSConcreteNotification 0xaa1fbb0 {name = NSObjectsChangedInManagingContextNotification; object = <NSManagedObjectContext: 0x745fa50>; userInfo = {
    managedObjectContext = "<NSManagedObjectContext: 0x745fa50>";
    refreshed = "{(\n    <HJBFoo: 0xaa1f9c0> (entity: HJBFoo; id: 0xaa1af50 <x-coredata://07E97098-E32D-45F6-9AB4-F9DAB9B0AC1A/HJBFoo/p1> ; data: {\n    name = 2;\n    show = 1;\n})\n)}";
}}

So the following things happen:

所以发生以下事情:

  • Changes are made in the "background" context managedObjectContext3 and saved.
  • 更改在“后台”上下文managedObjectContext3中进行并保存。
  • You receive a NSManagedObjectContextDidSaveNotification and managedObjectContextDidSave: is called. This notification contains the object that was updated in the background context.
  • 您收到NSManagedObjectContextDidSaveNotification和managedObjectContextDidSave:被调用。此通知包含在后台上下文中更新的对象。
  • Calling

    调用

    [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    

    now would not do anything, because the updated object has not been loaded into the fetchedResultsControllerManagedObjectContext or is a fault. In particular, this context would not post a NSManagedObjectContextObjectsDidChangeNotification and the fetched results controller would not update.

    现在不会做任何事情,因为更新的对象尚未加载到fetchedResultsControllerManagedObjectContext中或者是一个错误。特别是,此上下文不会发布NSManagedObjectContextObjectsDidChangeNotification,并且获取的结果控制器不会更新。

  • But calling willAccessValueForKey for the updated objects first forces the fetchedResultsControllerManagedObjectContext to load these objects and fire a fault.
  • 但是为更新的对象调用willAccessValueForKey会强制fetchedResultsControllerManagedObjectContext加载这些对象并触发故障。
  • Calling

    调用

    [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    

    after that actually updates the objects in the fetchedResultsControllerManagedObjectContext.

    之后实际更新fetchedResultsControllerManagedObjectContext中的对象。

  • Therefore a NSManagedObjectContextObjectsDidChangeNotification is posted and the fetched results controller calls the corresponding delegate functions.
  • 因此,将发布NSManagedObjectContextObjectsDidChangeNotification,并且获取的结果控制器将调用相应的委托函数。