当我修改 NSOperation 子类中的核心数据关系时,为什么我的应用程序崩溃了?

发布于 2024-09-27 12:01:05 字数 4618 浏览 0 评论 0原文

背景

我有以下对象树:

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

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

沙沙粒小 2024-10-04 12:01:05

您正在跨越流(在本例中为线程),这在 CoreData 中非常糟糕。这样看:

  1. 从主线程上的按钮(是 IBAction,假设按钮点击)调用 startTest
  2. 你的 for 循环使用初始化程序 trainGroup 创建一个 JGMakeRelationship 对象: withProject: (这应该称为 init,并且可能调用 super,但那是不会导致此问题)。
  3. 您可以在主线程上的操作中创建一个新的托管对象上下文。
  4. 现在,操作队列从工作线程调用操作“main”方法(在此处放置一个断点,您将看到它不在主线程上)。
  5. 您的应用程序会蓬勃发展,因为您从与创建托管对象上下文不同的线程访问托管对象上下文。

解决办法:

在操作的main方法中初始化托管对象上下文。

You are crossing the streams (threads in this case) which is very bad in CoreData. Look at it this way:

  1. startTest called from a button (is IBAction, assuming button tap) on Main thread
  2. Your for loop creates a JGMakeRelationship object using the initializer trainGroup: withProject: (this should be called init, and probably call super, but that's not causing this issue).
  3. You create a new managed object context in the operation, on the Main thread.
  4. Now the operation queue calls the operations "main" method from a worker thread (put a breakpoint here and you'll see it's not on the main thread).
  5. Your app goes boom because you've accessed a Managed object Context from a different thread than the one you created it on.

Solution:

Initialize the managed object context in the main method of the operation.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文