web-dev-qa-db-ja.com

NSManagedObjectをあるコンテキストから別のコンテキストにコピーまたは移動するにはどうすればよいですか?

保存されていないスクラッチパッドMOC(Webからダウンロードされた一連のオブジェクトを含む)と、オブジェクトを永続化する別の永続的なMOCがあるかなり標準的なセットアップだと思います。ユーザーがスクラッチMOCからオブジェクトを選択してライブラリに追加する場合、1)スクラッチMOCからオブジェクトを削除してパーマネントMOCに挿入するか、2)オブジェクトをパーマネントMOCにコピーします。 Core Data FAQ は次のようにオブジェクトをコピーできると言っています:

NSManagedObjectID *objectID = [managedObject objectID];
NSManagedObject *copy = [context2 objectWithID:objectID];

(この場合、context2はpermanentMOCになります。)しかし、これを行うと、コピーされたオブジェクトに障害が発生します。データは最初は未解決です。解決されると、後ですべての値がnilになります。元のmanagedObjectのデータ(属性または関係)が実際にコピーまたは参照されることはありません。したがって、このobjectWithID:メソッドを使用することと、insertNewObjectForEntityForName:を使用して完全に新しいオブジェクトをpermanentMOCに挿入するだけの違いはわかりません。

私は、permanentMOCで新しいオブジェクトを作成し、古いオブジェクトから各キーと値のペアを手動でコピーできることを理解していますが、その解決策にはあまり満足していません。 (私にはこの問題があるさまざまな管理対象オブジェクトがあるので、copy:メソッドの開発と開発を続けながら、それらすべてのメソッドを作成および更新する必要はありません。)より良い方法はありますか?

55
Aeonaut

まず、1つのスレッドで複数のNSManagedObjectContextを使用することは、標準構成ではありません。 99%の場合、必要なコンテキストは1つだけであり、それによってこの状況が解決されます。

複数のNSManagedObjectContextが必要だと思うのはなぜですか?

更新

これは、実際に私がこれまでに理解した数少ない使用例の1つです。これを行うには、あるコンテキストから別のコンテキストにオブジェクトの再帰的なコピーを行う必要があります。ワークフローは次のようになります。

  1. 永続的なコンテキストで新しいオブジェクトを作成する
  2. ソースオブジェクトから属性の辞書を取得します(これを行うには、-dictionaryWithValuesForKeysおよび-[NSEntityDescription attributesByName]を使用します。
  3. 値のディクショナリをターゲットオブジェクトに設定します(-setValuesForKeysWithDictionaryを使用)
  4. 関係がある場合は、このコピーを再帰的に実行し、ハードコーディング(循環ロジックを回避するため)または-[NSEntityDescription relationshipsByName]を使用して関係を歩く必要があります。

別の人が述べたように、私の本のサンプルコードを The Pragmatic Programmers Core Data Book からダウンロードして、この問題の1つの解決策を見ることができます。もちろん本の中で私はそれについてより深く議論します:)

52
Marcus S. Zarra

ドキュメントは誤解を招きやすく、不完全です。 objectIDメソッド自体はオブジェクトをコピーせず、必要な特定のオブジェクトを取得したことを保証するだけです。

この例のcontext2は、実際にはソースコンテキストであり、宛先ではありません。宛先コンテキストにそのIDのオブジェクトがないため、nilを取得しています。

管理対象オブジェクトのコピーは、オブジェクトグラフの複雑さとコンテキストによるグラフの管理方法のため、かなり複雑です。コピーしたオブジェクトを新しいコンテキストで詳細に再作成する必要があります。

ここにいくつかのサンプルコードがあります のサンプルコードから抜粋 The Pragmatic Programmer'sCore Data:Apple's API for Persisting Data on Mac OS X 。 (Pragmaticサイトで本を購入しなくてもプロジェクトコード全体をダウンロードできる場合があります。)コンテキスト間でオブジェクトをコピーする方法の大まかなアイデアが得られるはずです。

オブジェクトをコピーするいくつかの基本コードを作成できますが、各オブジェクトグラフの関係の詳細は通常、各データモデルごとにカスタマイズする必要があることを意味します。

9
TechZen

私自身も同じ問題を抱えていて、後でコンテキストに追加できる切断されたエンティティが作成されているこの記事を見つけました: http://locassa.com/temporary-storage-in-apples-coredata/

データベースにオブジェクトを格納するので、NSManagedObjectがあるという考えです。私のハードルは、これらのオブジェクトの多くがHTTP APIを介してダウンロードされ、セッションの最後にそれらのほとんどを破棄したいということでした。ユーザー投稿のストリームを考えてみてください。お気に入りに追加されたもの、または下書きとして保存されたものだけを保存したいと思います。

を使用してすべての投稿を作成します

+ (id)newPost {
    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:self.managedObjectContext];
    Post *post = [[Post alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:nil];
    return post;
}

投稿がお気に入りに追加されると、ローカルの管理オブジェクトコンテキストに挿入されます

+ (BOOL)favoritePost:(Post *)post isFavorite:(BOOL)isFavorite
{
    // Set the post's isFavorite flag
    post.isFavorite = [NSNumber numberWithBool:isFavorite];

    // If the post is being favorited and not yet in the local database, add it
    NSError *error;
    if (isFavorite && [self.managedObjectContext existingObjectWithID:post.objectID error:&error] == nil) {
        [self.managedObjectContext insertObject:post];
    }
    // Else if the post is being un-favorited and is in the local database, delete it
    else if (!isFavorite && [self.managedObjectContext existingObjectWithID:post.objectID error:&error] != nil) {
        [self.managedObjectContext deleteObject:post];
    }

    // If there was an error, output and return NO to indicate a failure
    if (error) {
        NSLog(@"error: %@", error);
        return NO;
    }

    return YES;
}

お役に立てば幸いです。

6
Anthony

managedObjectが存在するコンテキストを保存していることを確認する必要があります。同じオブジェクトを別のコンテキストでフェッチするには、永続ストアに存在している必要があります。

ドキュメント によると、objectWithID:は常にオブジェクトを返します。したがって、障害がオブジェクトに解決されるという事実は、すべてのnil値が永続ストアでオブジェクトを見つけられないことを意味します。

1
Alex

Swift 5

2020年にNSManagedObjectsをコピーする方法を知りたい場合は、以下のコードでうまくいきます。

// `Restaurant` is the name of my managed object subclass.

// I like to have Xcode auto generate my subclasses (Codegen 
//     set to "Class Definition") & then just extend them with 
//     whatever functionality I need.

extension Restaurant {
    public func copy() -> Restaurant? {
        let attributes = entity.attributesByName.map { $0.key }
        let dictionary = dictionaryWithValues(forKeys: attributes)
        guard let context = AppDelegate.shared?.persistentContainer.viewContext,
            let restaurantCopy = NSEntityDescription.insertNewObject(forEntityName: Restaurant.entityName, into: context) as? Restaurant
            else
        {
            return nil
        }
        restaurantCopy.setValuesForKeys(dictionary)

        return restaurantCopy
    }
}
0
Trev14