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

时间:2012-12-24 06:49:45

标签: objective-c ios core-data nsfetchedresultscontroller

  1. 我填充并save:初始NSManagedObjectContext
  2. 使用不同的NSFetchedResultsController设置NSManagedObjectContext,其过滤boolean" show"属性。
  3. 最后更新"显示"另一个NSManagedObjectContextsave:
  4. 我希望这会导致我的NSFetchedResultsController致电NSFetchedResultsControllerDelegate controllerDidChangeContent:。我从来没有接到那个电话。 NSFetchedResultsController with predicate ignores changes merged from different NSManagedObjectContext accepted answer表示除controllerDidChangeContent:之外,我应该收到NSManagedObjectContextObjectsDidChangeNotification,但我也没有收到。{/ p >

    下面列出了完整的代码示例on githubI've filed a radar with 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 个答案:

答案 0 :(得分:17)

在我看来,应用NSFetchedResultsController with predicate ignores changes merged from different 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://stackoverflow.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];
    }
}

并且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)}";
}}

所以发生以下事情:

  • 更改在“背景”上下文managedObjectContext3中进行并保存。
  • 您收到了NSManagedObjectContextDidSaveNotification并且调用了managedObjectContextDidSave:。此通知包含在后台上下文中更新的对象。
  • 调用

    [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    

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

  • 但是,为更新的对象调用willAccessValueForKey会强制fetchedResultsControllerManagedObjectContext加载这些对象并触发故障。
  • 调用

    [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    

    之后实际更新fetchedResultsControllerManagedObjectContext

  • 中的对象
  • 因此发布了NSManagedObjectContextObjectsDidChangeNotification,并且获取的结果控制器调用相应的委托函数。