- I populate and
save:
an initialNSManagedObjectContext
- 我填充并保存:一个初始的NSManagedObjectContext
- setup an
NSFetchedResultsController
with a differentNSManagedObjectContext
, which filters on aboolean
"show" attribute. - 使用不同的NSManagedObjectContext设置NSFetchedResultsController,它过滤布尔“show”属性。
- Finally update "show" on yet another
NSManagedObjectContext
andsave:
. - 最后在另一个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
andmanagedObjectContextDidSave:
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 aNSManagedObjectContextObjectsDidChangeNotification
and the fetched results controller would not update.现在不会做任何事情,因为更新的对象尚未加载到fetchedResultsControllerManagedObjectContext中或者是一个错误。特别是,此上下文不会发布NSManagedObjectContextObjectsDidChangeNotification,并且获取的结果控制器不会更新。
- But calling
willAccessValueForKey
for the updated objects first forces thefetchedResultsControllerManagedObjectContext
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
andmanagedObjectContextDidSave:
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 aNSManagedObjectContextObjectsDidChangeNotification
and the fetched results controller would not update.现在不会做任何事情,因为更新的对象尚未加载到fetchedResultsControllerManagedObjectContext中或者是一个错误。特别是,此上下文不会发布NSManagedObjectContextObjectsDidChangeNotification,并且获取的结果控制器不会更新。
- But calling
willAccessValueForKey
for the updated objects first forces thefetchedResultsControllerManagedObjectContext
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,并且获取的结果控制器将调用相应的委托函数。