10

バックグラウンド

次のオブジェクトのツリーがあります。

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おり、メインのコンテキストを監視してマージしています。

問題

保存が次のエラーで失敗しています:

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.

私が試したこと

アプリの複雑さをすべて取り除き、考えられる最も単純なプロジェクトを作成しました。そして、エラーはまだ発生します。私はもう試した:

  • キューの操作の最大数を 1 または 10 に設定します。

  • NSOperationrefreshObject:mergeChanges:サブクラスのいくつかのポイントでの呼び出し。

  • 管理対象オブジェクト コンテキストにマージ ポリシーを設定します。

  • 構築して分析します。それは空になります。

私の質問

アプリをクラッシュさせずに 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 が文句を言います。

スレッドのコーディネーターを作成する方法について提案したいと思います。コア データ ドキュメントには、それを行う方法があることがほのめかされていますが、方法がわかりません。

4

1 に答える 1

10

CoreData では非常に悪いストリーム (この場合はスレッド) を越えています。このように見てください:

  1. メイン スレッドのボタンから呼び出される startTest (ボタン タップを想定して、IBAction です)
  2. for ループは、初期化子 trainGroup: withProject: を使用して JGMakeRelationship オブジェクトを作成します (これは init と呼ばれ、おそらく super を呼び出す必要がありますが、この問題は発生していません)。
  3. メイン スレッドの操作で、新しい管理対象オブジェクト コンテキストを作成します。
  4. これで、オペレーション キューはワーカー スレッドからオペレーションの「メイン」メソッドを呼び出します (ここにブレークポイントを設定すると、メイン スレッド上にないことがわかります)。
  5. アプリを作成したスレッドとは別のスレッドからマネージド オブジェクト コンテキストにアクセスしたため、アプリがブームになりました。

解決:

操作のメイン メソッドで管理オブジェクト コンテキストを初期化します。

于 2010-10-20T08:17:04.720 に答える