NSFetchedResultsController不会调用controllerDidChangeContent:更新到未获取的NSManagedObject

  1. 我填充并save:一个初始的NSManagedObjectContext
  2. 用一个不同的NSManagedObjectContext设置一个NSManagedObjectContext ,它过滤一个boolean “show”属性。
  3. 最后在另一个NSManagedObjectContext上更新“show”并save:

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

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

 @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 

在我看来,从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://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中进行更改并保存。
  • 您会收到一个NSManagedObjectContextDidSaveNotificationmanagedObjectContextDidSave:被调用。 此通知包含在后台上下文中更新的对象。
  • 调用

     [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; 

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

  • 但是为更新的对象调用willAccessValueForKey将首先强制fetchedResultsControllerManagedObjectContext加载这些对象并fetchedResultsControllerManagedObjectContext错误。
  • 调用

     [self.fetchedResultsControllerManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; 

    之后实际更新fetchedResultsControllerManagedObjectContext的对象。

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