当我修改 NSOperation 子类中的核心数据关系时,为什么我的应用程序崩溃了?
背景
我有以下对象树:
Name Project
Users nil
John nil
Documents nil
Acme Project Acme Project <--- User selects a project
Proposal.doc Acme Project
12:32-12:33 Acme Project
13:11-13:33 Acme Project
...thousands more entries here...
用户可以将组分配给项目。所有后代都设置为该项目。
这会锁定主线程,因此我正在使用 NSOperations。
我正在使用 Apple 批准的方法来执行此操作,监视
NSManagedObjectContextDidSaveNotification
并合并到主上下文中。
问题
我的保存失败并出现以下错误:
无法在保存前处理挂起的更改。经过 100 次尝试后,上下文仍然是脏的。通常,这种递归脏乱是由错误的验证方法、-willSave 或通知处理程序引起的。
我的尝试
我已经剥离了应用程序的所有复杂性,并制作了我能想到的最简单的项目。并且错误仍然发生。我尝试过:
将队列上的最大操作数设置为 1 或 10。
调用
refreshObject:mergeChanges :
在 NSOperation 子类中的多个点。在托管对象上下文上设置合并策略。
构建和分析。结果是空的。
我的问题
如何在 NSOperation 中设置关系而不让我的应用程序崩溃?这肯定不是 Core Data 的限制吧?可以吗?
代码
下载我的项目: http://synapticmishap.co.uk/CDMTTest1.zip
主控制器
@implementation JGMainController
-(IBAction)startTest:(id)sender {
NSManagedObjectContext *imoc = [[NSApp delegate] managedObjectContext];
JGProject *newProject = [JGProject insertInManagedObjectContext:imoc];
[newProject setProjectName:@"Project"];
[imoc save];
// Make an Operation Queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1]; // Also crashes with a higher number here (unsurprisingly)
NSSet *allTrainingGroupsSet = [imoc fetchAllObjectsForEntityName:@"TrainingGroup"];
for(JGTrainingGroup *thisTrainingGroup in allTrainingGroupsSet) {
JGMakeRelationship *makeRelationshipOperation = [[JGMakeRelationship alloc] trainGroup:[thisTrainingGroup objectID] withProject:[newProject objectID]];
[queue addOperation:makeRelationshipOperation];
makeRelationshipOperation = nil;
}
}
// Called on app launch.
-(void)setupLotsOfTestData {
// Sets up 10000 groups and one project
}
@end
建立关系操作
@implementation JGMakeRelationshipOperation
-(id)trainGroup:(NSManagedObjectID *)groupObjectID_ withProject:(NSManagedObjectID *)projectObjectID_ {
appDelegate = [NSApp delegate];
imoc = [[NSManagedObjectContext alloc] init];
[imoc setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
[imoc setUndoManager:nil];
[imoc setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:imoc];
groupObjectID = groupObjectID_;
projectObjectID = projectObjectID_;
return self;
}
-(void)main {
JGProject *project = (JGProject *)[imoc objectWithID:projectObjectID];
JGTrainingGroup *trainingGroup = (JGTrainingGroup *)[imoc objectWithID:groupObjectID];
[project addGroupsAssignedObject:trainingGroup];
[imoc save];
trainingGroupObjectIDs = nil;
projectObjectID = nil;
project = nil;
trainingGroup = nil;
}
-(void)mergeChanges:(NSNotification *)notification {
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
[mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}
-(void)finalize {
appDelegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
imoc = nil;
[super finalize];
}
@end
@implementation NSManagedObjectContext (JGUtilities)
-(BOOL)save {
// If there's an save error, I throw an exception
}
@end
数据模型
更新1
我又尝试了一些,即使没有合并,仍然抛出异常。修改关系后,只需将托管对象上下文保存在另一个线程中就足够了。
我与应用程序委托有一个共享的持久存储协调器。我尝试为与我的数据存储具有相同 URL 的线程创建一个单独的 NSPersistentStoreCoordinator,但 Core Data 抱怨。
我很想就如何为该线程创建协调员提出建议。核心数据文档暗示有一种方法可以做到这一点,但我不知道如何做到。
Background
I've got the following tree of objects:
Name Project
Users nil
John nil
Documents nil
Acme Project Acme Project <--- User selects a project
Proposal.doc Acme Project
12:32-12:33 Acme Project
13:11-13:33 Acme Project
...thousands more entries here...
The user can assign a group to a project. All descendants get set to that project.
This locks up the main thread so I'm using NSOperations.
I'm using the Apple approved way of doing this, watching for
NSManagedObjectContextDidSaveNotification
and merging into the main context.
The Problem
My saves have been failing with the following error:
Failed to process pending changes before save. The context is still dirty after 100 attempts. Typically this recursive dirtying is caused by a bad validation method, -willSave, or notification handler.
What I've Tried
I've stripped all the complexities of my app away, and made the simplest project I could think of. And the error still occurs. I've tried:
Setting the max number of operations on the queue to 1 or 10.
Calling
refreshObject:mergeChanges:
at several points in the NSOperation subclass.Setting merge policies on the managed object context.
Build and Analyze. It comes up empty.
My Question
How do I set relationships in an NSOperation without my app crashing? Surely this can't be a limitation of Core Data? Can it?
The Code
Download my project: http://synapticmishap.co.uk/CDMTTest1.zip
Main Controller
@implementation JGMainController
-(IBAction)startTest:(id)sender {
NSManagedObjectContext *imoc = [[NSApp delegate] managedObjectContext];
JGProject *newProject = [JGProject insertInManagedObjectContext:imoc];
[newProject setProjectName:@"Project"];
[imoc save];
// Make an Operation Queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1]; // Also crashes with a higher number here (unsurprisingly)
NSSet *allTrainingGroupsSet = [imoc fetchAllObjectsForEntityName:@"TrainingGroup"];
for(JGTrainingGroup *thisTrainingGroup in allTrainingGroupsSet) {
JGMakeRelationship *makeRelationshipOperation = [[JGMakeRelationship alloc] trainGroup:[thisTrainingGroup objectID] withProject:[newProject objectID]];
[queue addOperation:makeRelationshipOperation];
makeRelationshipOperation = nil;
}
}
// Called on app launch.
-(void)setupLotsOfTestData {
// Sets up 10000 groups and one project
}
@end
Make Relationship Operation
@implementation JGMakeRelationshipOperation
-(id)trainGroup:(NSManagedObjectID *)groupObjectID_ withProject:(NSManagedObjectID *)projectObjectID_ {
appDelegate = [NSApp delegate];
imoc = [[NSManagedObjectContext alloc] init];
[imoc setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
[imoc setUndoManager:nil];
[imoc setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:imoc];
groupObjectID = groupObjectID_;
projectObjectID = projectObjectID_;
return self;
}
-(void)main {
JGProject *project = (JGProject *)[imoc objectWithID:projectObjectID];
JGTrainingGroup *trainingGroup = (JGTrainingGroup *)[imoc objectWithID:groupObjectID];
[project addGroupsAssignedObject:trainingGroup];
[imoc save];
trainingGroupObjectIDs = nil;
projectObjectID = nil;
project = nil;
trainingGroup = nil;
}
-(void)mergeChanges:(NSNotification *)notification {
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
[mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}
-(void)finalize {
appDelegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
imoc = nil;
[super finalize];
}
@end
@implementation NSManagedObjectContext (JGUtilities)
-(BOOL)save {
// If there's an save error, I throw an exception
}
@end
Data Model
Update 1
I've experimented some more, and even without the merge, the exception is still thrown. Just saving the managed object context in another thread after modifying a relationship is enough.
I have a shared persistent store coordinator with the app delegate. I've tried making a separate NSPersistentStoreCoordinator for the thread with the same URL as my data store, but Core Data complains.
I'd love to suggestions on how I can make a coordinator for the thread. The core data docs allude to there being a way of doing it, but I can't see how.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您正在跨越流(在本例中为线程),这在 CoreData 中非常糟糕。这样看:
解决办法:
在操作的main方法中初始化托管对象上下文。
You are crossing the streams (threads in this case) which is very bad in CoreData. Look at it this way:
Solution:
Initialize the managed object context in the main method of the operation.