3

MOC の 1 つが一貫性のない状態になる原因となる、CoreData での奇妙な動作が発生しています。小さなサンプルプロジェクトで問題を再現しました

これが私の状況の基本的な要約です。

  • パイプラインとボックスの 2 つのエンティティ タイプがあります。各パイプラインには 0 個以上のボックスを含めることができ、各ボックスは 1 つのパイプラインの一部です

  • 私のサンプルプロジェクトでは:

    • 1 つのパイプラインと 3 つのサンプル ボックスをすべてそのパイプラインに向けて作成しています。
    • このサンプル データは、で作成された MOC で作成されます。NSMainQueueConcurrencyType
    • バックグラウンド MOC (「NSPrivateQueueConcurrencyType」) を作成し、すべてのボックスをフェッチして、そのうちの 1 つだけを削除します。
    • この削除により、パイプラインが更新されます (関係のボックスが 1 つ少なくなるはずです)。
    • バックグラウンド MOC を保存すると、変更をメイン キュー MOC にマージしようとします。

問題は、マージ後、メイン キュー コンテキストが削除を正常にマージしたが、編集をパイプラインにマージしなかったことです。これは、リレーション内のボックスが 1 つ少ないことを示しているはずです。

どういうわけか、変更全体をマージしていません。

コードの一部を次に示します。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

    // create a core database and a moc on the ui thread
    [self initCoreData];

    // fill up db with dummy data all on the main thread
    [self createDummyData:self.uiContext];

    [self printUIContextContents];

    // now create a background moc
    NSManagedObjectContext* backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

    // when the backgrouynd context saves, merge changees in to UI context
    [backgroundContext performBlockAndWait:^{
        backgroundContext.mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
        [backgroundContext setPersistentStoreCoordinator:self.persistentCoordinator];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mocDidSave:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];
    }];


    // now delete a box on background thread
    [backgroundContext performBlock:^{
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Box"];
        request.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"box1"];

        NSError* error = nil;
        NSArray* boxes = [backgroundContext executeFetchRequest:request error:nil];
        if (error != nil){
            NSLog(@"Error in deleting box: %@", error);
        }

        Box* box = (Box*)boxes[0];

        [backgroundContext deleteObject:box];
        [backgroundContext save:nil];
    }];

    [self printUIContextContents];


    return YES;
}

- (void) printUIContextContents {
    [self.uiContext performBlockAndWait:^{
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Pipeline"];
        NSArray* pipelines = [self.uiContext executeFetchRequest:request error:nil];
        for (Pipeline* pipeline in pipelines) {
            NSLog(@"Pipeline Name: %@", pipeline.name);
            NSLog(@"\tBoxes that are in the pipeline relationship: ");
            for (Box* box in pipeline.boxes) {
                NSLog(@"\t\t%@", box.name);
            }
        }

        NSLog(@" ");

        request = [NSFetchRequest fetchRequestWithEntityName:@"Box"];
        NSArray* boxes = [self.uiContext executeFetchRequest:request error:nil];
        NSLog(@"All Boxes Entities Present:");
        for (Box* box in boxes) {
            NSLog(@"\t%@", box.name);
        }
        NSLog(@" ");
        NSLog(@" ");
    }];
}

- (void)mocDidSave:(NSNotification *)notif {

    [self.uiContext performBlockAndWait:^(void) {
        [self.uiContext mergeChangesFromContextDidSaveNotification:notif];
    }];
}


- (void) createDummyData:(NSManagedObjectContext*)context {
    NSArray* boxNames = [NSArray arrayWithObjects:@"box1", @"box2", @"box3", nil];
    NSString* pipelineName = @"pipeline1";

    [self.uiContext performBlockAndWait:^{
        Pipeline* pipe = (Pipeline*)[self createEntity:@"Pipeline" inContext:self.uiContext];
        pipe.name = pipelineName;

        for (NSString* boxName in boxNames) {
            Box* box = (Box*)[self createEntity:@"Box" inContext:self.uiContext];
            box.name = boxName;
            box.pipeline = pipe;
        }

        NSError* error = nil;
        [self.uiContext save:&error];
        if (error != nil){
            NSLog(@"Error in create dummy data: %@", error);
        }
    }];
}


- (void) initCoreData {
    NSURL* modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
    NSURL* storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"StreakDB.sqlite"];
    [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];

    NSManagedObjectModel* objectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    NSError *error = nil;
    self.persistentCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:objectModel];
    if (![self.persistentCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        /*
         SOME ERROR HANDLING HERE
         */
    }

    self.uiContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [self.uiContext setPersistentStoreCoordinator:self.persistentCoordinator];
    self.uiContext.mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
}

- (NSManagedObject*)createEntity:(NSString *)entityType inContext:(NSManagedObjectContext *)context {
    return [NSEntityDescription insertNewObjectForEntityForName:entityType
                                         inManagedObjectContext:context];
}

何が起こっているのでしょうか?ここでも、この問題を説明する (そして結果を出力する) サンプル プロジェクトを示します

アップデート:

パイプライン名:pipeline1 パイプライン関係にあるボックス:box3 box1 box2

存在するすべてのボックス エンティティ: box3 box2 box1

パイプライン名:pipeline1 パイプライン関係にあるボックス:box3 box1 box2

存在するすべてのボックス エンティティ: box3 box2

ご覧のとおり、2 回目に uiContext を出力すると、一貫性のない状態になります。具体的には、コンテキストには 2 つのボックスがありますが、パイプラインには 3 つのボックスを指す関係があるため、矛盾が生じています。

バックグラウンド保存が 2 回目の印刷の前または後に完了している可能性があることは理解していますが、どちらの場合でも、コンテキストの状態は一貫しているはずです。(つまり、3 つのボックスと 3 つのアイテムの関係、または 2 つのボックスと 2 つのアイテムの関係)。

4

1 に答える 1

1

これは、スレッド化の問題である可能性があります (BG 操作を並行して実行しています)。次のように変更してみてください。[backgroundContext performBlockAndWait: ...];

詳細:
コンテキストは矛盾した状態ではありません。
なんで?

0: 各フェッチ要求はストアへのトリップであり、実行時にコンテキストに含まれるデータに依存しません。取得されたオブジェクトは、既存のアイテム情報を再利用するために、コンテキストの既存の行キャッシュと照合されます。コンテキスト内のアイテムの重複を防ぐため、現在のストア ステータスのスナップショットを取得します。

1. MOC1
(メイン コンテキスト) でアイテムを作成し、2 フェーズのフェッチ要求を使用してオブジェクトをログに記録します。
1.1。(0) 以降、基になるストアがフェッチ間で変更された場合、各フェッチ要求は異なるデータ セットを返す可能性があります
2. 別のスレッドを使用して操作を実行する MOC2 を作成しますが、その作成によって MOC1 のスレッド (メイン スレッド) がブロックされます。
2.1. MOC2 スレッドを使用して非同期ブロックを実行します。
2.2. MOC2 はいくつかのオブジェクトを削除しています
2.3. MOC2 はその変更を保存します (ストアはマージ前にここで変更されました)
2.4. MOC2 は変更を MOC1 にマージしようとしていますが、MOC1 のスレッドがログ関数の実行でビジーであるため、マージできません ((3.) を参照)。
3. 注意メイン スレッドが再びログ関数に入り、現在の実行ループがまだ終了していないため (2.1. 操作と並行して)、メインの実行ループがそのサイクルを終了するまで MOC1 へのマージはできません。
3.1. MOC1 ログ関数の実行 最初のフェッチ要求 (おそらく、MOC2 が変更をストアに保存する前に、この要求はコーディネーターをブロックするため、MOC2 が保存する準備ができていても、最初のフェッチが完了するまでコーディネーターはブロックされます) 3.1.1 .
box1,box2,box3 を取得する 3.2. MOC1 が 2 番目のフェッチ要求を実行する (おそらく、MOC2 がストアに保存された後) 3.2.1. box2,box3 を取得する 4. メインの実行ループの終了と MOC2その変更を MOC1 にマージできるようになりました

これが少し明確になることを願っています。

変更を MOC1 にマージした後にログを記録してみて、本来あるべき状態であることを確認してください。

複数のスレッドを扱う場合は、通知を使用するか (以前と同様)、親子アーキテクチャを使用して MOC を同期する必要があります。

親子アーキテクチャでは、これは発生しませんが、ストアへの実際の書き込みを行うコンテキストがメイン コンテキストになり、メイン スレッドがブロックされます。

于 2013-05-08T05:18:00.797 に答える