web-dev-qa-db-ja.com

親/子NSManagedObjectContextの正しい実装

私のアプリは、必ずしも保存する必要がないオブジェクトを管理オブジェクトコンテキストに挿入することがあります。たとえば、「エンティティの追加」モーダルを起動すると、管理対象オブジェクトが作成され、モーダルに割り当てられます。ユーザーがそのモーダルから保存する場合は、コンテキストを保存します。彼がキャンセルした場合、オブジェクトを削除し、保存は必要ありません。

アプリに切り替え(URLスキームを使用)、エンティティを追加する「インポート」機能を導入しました。これらのモーダルの1つが開いている可能性があるため、この時点でコンテキストを保存することは安全ではありません。ユーザーがキャンセルした場合でも、モーダル用に作成された一時オブジェクトは保存されます。(キャンセル操作による)削除が後で保存される保証はありません。ユーザーがアプリを終了する場合があります。

同様に、アプリが終了するたびに保存することはできません。その時点でモーダルが開いている場合、一時オブジェクトは誤って保存されます。

これに対処するために、私は here で説明されているように、子コンテキストを使用しようとしています。私が見つけることができるすべてを読んだ後、私はいくつかの質問をします:

  1. 各コンテキストでどの並行性タイプを使用する必要がありますか?パフォーマンス/スレッドの利点のためにこれを行っているわけではないことを覚えておいてください。子コンテキストを持つ場合、メインコンテキストにNSConfinementConcurrencyTypeを使用できないことはわかっていますが、他の2つのオプションのどちらが最適であるかはわかりません。子コンテキストの場合、一致する必要がありますか?または、ここで閉じ込めタイプを使用することもできますか?私はさまざまな組み合わせを試してみましたが、すべてうまくいくようですが、自分の要件にどちらが適切かを知りたいのですが。

  2. (副問題)クラスiVarを使用する場合にのみこれを機能させることができるのはなぜですか?一時的なコンテキストを作成するメソッドで宣言し、後でentity.managedObjectContextを使用してそれを参照できるようにする必要があると思いました。しかし、私がそれにアクセスするようになるまでに、それはゼロであるように見えますか?代わりにiVarを使用して参照を保持する場合、これは修正されます。

  3. 正しい方法またはメインコンテキストへの変更の伝播は何ですか?各コンテキストで異なるブロックラップ実装を使用したさまざまなコメントを見てきました。それは私の並行性タイプに依存しますか?私の現在のバージョンは:

    //save the new entity in the temporary context
    NSError *error = nil;
    if (![myObject.managedObjectContext save:&error]) {NSLog(@"Error - unable to save new object in its (temporary) context");}
    
    //propogate the save to the main context
    [self.mainContext performBlock:^{
        NSError *error2 = nil;
        if (![self.mainContext save:&error2]) {NSLog(@"Error - unable to merge new entity into main context");}
    }];
    
  4. ユーザーが保存すると、デリゲート(メインビューコントローラー)にメッセージが送信されます。デリゲートには追加されたオブジェクトが渡され、メインコンテキストで同じオブジェクトを見つける必要があります。しかし、メインコンテキストで検索すると、見つかりません。メインコンテキストにはエンティティが含まれます-詳細をログに記録して、そこにあることを確認できますが、アドレスが異なりますか?これが発生することを意図している場合(なぜですか?)、保存後にメインコンテキストで追加されたオブジェクトを見つけるにはどうすればよいですか?

洞察をありがとう。複数の部分からなる長い質問で申し訳ありませんが、誰かが以前にこれらの問題すべてに対処した可能性が高いと思いました。

27
Ben Packard

親子MOCモデルは、コアデータの非常に強力な機能です。これは、私たちが対処しなければならなかった古くからある並行処理の問題を驚くほど単純化します。ただし、すでに述べたように、同時実行性は問題ではありません。あなたの質問に答えるには:

  1. 従来、メインスレッドに関連付けられたNSMainQueueConcurrencyTypeにはNSManagedObjectContextを使用し、子コンテキストにはNSPrivateQueueConcurrencyTypesを使用します。子コンテキストは、その親と一致する必要はありません。タイプを指定しない場合、NSConfinementConcurrencyTypeはすべてのNSManagedObjectContextsがデフォルトで使用するものです。基本的には、「コアデータ用に自分のスレッドを管理します」タイプです。
  2. あなたのコードを見なければ、私の仮定はあなたが子コンテキストを作成するスコープが終わり、それがクリーンアップされることです。
  3. 親/子コンテキストパターンを使用する場合は、ブロックメソッドを使用する必要があります。ブロックメソッドを使用する最大の利点は、OSがメソッド呼び出しを正しいスレッドにディスパッチすることを処理できることです。非同期実行にはperformBlockを、同期実行にはperformBlockAndWaitを使用できます。

次のように使用します。

- (void)saveContexts {
    [childContext performBlock:^{
        NSError *childError = nil;
        if ([childContext save:&childError]) {
            [parentContext performBlock:^{
                NSError *parentError = nil;
                if (![parentContext save:&parentError]) {
                    NSLog(@"Error saving parent");
                }
            }];
        } else {
            NSLog(@"Error saving child");
        }
    }];
}

ここで、子コンテキストで行われた変更(挿入されたエンティティなど)は、保存するまで親コンテキストで使用できないことに注意してください。子コンテキストにとって、親コンテキストは永続ストアです。保存するとき、それらの変更を親に渡し、親はそれらを実際の永続ストアに保存できます。プロパゲートの変更を1レベル上に保存します。一方、子コンテキストにフェッチすると、データがすべてのレベルで(親を介して子に)プルダウンされます。

  1. ManagedObjectContextで何らかの形式のobjectWithIDを使用する必要があります。これらは、コンテキスト間でオブジェクトを渡す最も安全な(そして本当に唯一の)方法です。 Tom Harringtonがコメントで述べたように、existingObjectWithID:error:理由はobjectWithID:は、無効なIDを渡した場合でも、常にオブジェクトを返します(例外が発生する可能性があります)。詳細: リンク
47
jmstone617

私は同様の問題を抱えていて、ここにあなたの質問の一部への回答があります-1.同時実行タイプNSPrivateQueueConcurrencyTypeまたはNSMainQueueConcurrencyTypeを使用できるはずです2.一時的なコンテキストを作成したとしますtempContextと親コンテキストmainContext(これはiOS5を想定しています)。その場合、管理オブジェクトをtempContextからmainContextに移動するだけで

object = (Object *)[mainContext objectWithID:object.objectID];

その後、mainContext自体を保存できます。

たぶん、

[childContext reset];

一時的なコンテキストをリセットする場合。

6
Devang
  1. 親/子パターンを使用する場合、通常はNSMainQueueConcurrencyTypeで親コンテキストを宣言し、NSPrivateQueueConcurrencyTypeで子コンテキストを宣言します。 NSConfinementConcurrencyTypeは、従来のスレッドパターンに使用されます。

  2. コンテキストを保持したい場合は、どういうわけかそれへの強い参照が必要です。

  3. 子コンテキストでsaveメソッドを呼び出すだけで、変更を親コンテキストにプッシュできます。データを永続化したい場合は、親コンテキストでもsaveを呼び出します。ブロック内でこれを行う必要はありません。

  4. コンテキストから特定のオブジェクトを取得する方法はいくつかあります。私はあなたのケースでどれがうまくいくかあなたに言うことができません、それらを試してください:

    - objectRegisteredForID:

    - objectWithID:

    - existingObjectWithID:error:

6
Andy