14

Core Data モデルに Departments と Employees という 2 つのエンティティがあるとします。
Department は Employees と 1 対多の関係にあります。

次の ManagedObjectContexts があります:
- ルート: 永続ストア コーディネーターに接続されてい
ます - メイン: 親ルートを持つコンテキスト

Employee を作成したいときは、次のようにします。
- Main コンテキストに Department があります
- Main コンテキストに Employee を作成します
- Department を Employee の department プロパティに割り当てます -
Main コンテキスト
を保存します -ルート コンテキスト

これにより、Main コンテキストと Root コンテキストの両方で保持サイクルが作成されます。

refreshObject:mergeChanges子コンテキストなしで (すべてルート コンテキストで) これを行った場合、Employeeを呼び出して保持サイクルを中断できます。2 つのコンテキストがある私の状況では、そのメソッドを使用してメイン コンテキストのサイクルを中断することはできますが、ルート コンテキストのサイクルを中断するにはどうすればよいでしょうか?

補足:これは私の問題を説明する簡単な例です。Instruments では、割り当て数が増えていることがはっきりとわかります。私のアプリでは、保存しているコンテキストごとに保持サイクルを持つ新しいエンティティ割り当てを取得するため、1 レベルよりも深いコンテキストがあり、さらに大きな問題を引き起こしています。

Update 15/04: NSPrivateQueueConcurrencyType と NSMainQueueConcurrencyType
両方のコンテキストを保存した後refreshObject:mergeChanges、Main コンテキストで Department オブジェクトを使用して実行できます。これにより、予想どおり、Department オブジェクトが再フォールトし、保持サイクルが中断され、そのコンテキストで Department および Employee エンティティの割り当てが解除されます。

次のステップは、ルート コンテキストに存在する保持サイクルを断ち切ることです (メイン コンテキストを保存すると、エンティティがルート コンテキストに伝播されます)。ここで同じトリックを実行しrefreshObject:mergeChangesて、ルート コンテキストで Department オブジェクトを使用できます。

奇妙なこと: これは、ルート コンテキストが NSMainQueueConcurrencyType で作成された場合 (すべての割り当てが再フォールトされ、解放された場合) は正常に機能しますが、ルート コンテキストが NSPrivateQueueConcurrencyType で作成された場合は機能しません (すべての割り当ては再フォールトされましたが、解放されていません)。 )。

補足: ルート コンテキストのすべての操作は、 performBlock(AndWait) 呼び出しで行われます

2004 年 15 月更新: パート 2 NSPrivateQueueConcurrencyType
を使用してルート コンテキストで別の (変更がないため役に立たない) 保存またはロールバックを行うと、オブジェクトの割り当てが解除されたように見えます。これが NSMainQueueConcurrencyType と同じように動作しない理由がわかりません。

Update 16/04: デモ プロジェクト デモ プロジェクト
を作成しました: http://codegazer.com/code/CoreDataTest.zip

2004 年 21 月更新: 行き方
Jody Hagings さん、ご協力ありがとうございます。ManagedObjectメソッド
から移動しようとしています。refreshObject:mergeChangesdidSave

次の違いを説明していただけますか。

[rootContext performBlock:^{
    [rootContext save:nil];
    for (NSManagedObject *mo in rootContext.registeredObjects)
        [rootContext refreshObject:mo mergeChanges:NO];
}];

[rootContext performBlock:^{
    [rootContext save:nil];
    [rootContext performBlock:^{
        for (NSManagedObject *mo in rootContext.registeredObjects)
            [rootContext refreshObject:mo mergeChanges:NO];
    }];
}];

上のものはオブジェクトの割り当てを解除しませんが、下のものは割り当てを解除します。

4

3 に答える 3

10

あなたのサンプルプロジェクトを見ました。投稿ありがとうございます。

まず、あなたが見ている動作はバグではありません...少なくともCore Dataではそうではありません。ご存じのように、リレーションシップは保持サイクルを引き起こし、手動で破棄する必要があります ( https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdMemory.htmlに記載されています)。

あなたのコードは でこれを行っていdidSave:ます。サイクルを断ち切るためのより良い場所があるかもしれませんが、それは別の問題です.

プロパティを見ると、どのオブジェクトが MOC に登録されているかを簡単に確認できregisteredObjectsます。

processPendingEventsただし、そのMOCで呼び出されることはないため、この例ではルートコンテキストで参照が解放されることはありません。したがって、MOC に登録されたオブジェクトは解放されません。

Core Dataには「ユーザーイベント」という概念があります。デフォルトでは、「ユーザー イベント」はメインの実行ループで適切にラップされます。

ただし、メイン スレッドにない MOC については、ユーザー イベントが適切に処理されるようにする責任があります。このドキュメントを参照してください: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html、具体的には というタイトルのセクションの最後の段落Track Changes in Other Threads Using Notifications

指定したブロックを呼び出すとperformBlock、完全なユーザー イベント内にラップされます。ただし、 の場合はこの限りではありませんperformBlockAndWait。したがって、プライベート コンテキスト MOC は、が呼び出されるregisteredObjectsまで、これらのオブジェクトをコレクションに保持します。processPendingChanges

あなたの例では、processPendingChanges内で呼び出すかperformBlockAndWait、 に変更すると、オブジェクトが解放されるのを確認できますperformBlock。これらのいずれかにより、MOC が現在のユーザー イベントを完了し、registeredObjectsコレクションからオブジェクトを削除することが保証されます。

編集

あなたの編集に応じて...最初のものはオブジェクトの割り当てを解除しないというわけではありません。これは、MOC に障害として登録されたオブジェクトがまだあるということです。それは保存後、同じイベント中に起こりました。単純に no-op ブロック[context performBlock:^{}]を発行すると、オブジェクトが MOC から削除されます。

したがって、その MOC の次の操作でオブジェクトがクリアされるため、心配する必要はありません。とにかく何もしない長時間のバックグラウンド MOC を持つべきではないので、これは実際には大したことではありません。

一般に、すべてのオブジェクトを単にリフレッシュすることは望ましくありません。ただし、保存後にすべてのオブジェクトを削除したい場合はdidSave:、保存プロセス中に発生するため、元のコンセプトであるそれを実行することは合理的です。ただし、これはすべてのコンテキストでオブジェクトを失敗させます (これはおそらく望ましくありません)。おそらく、バックグラウンド MOC にはこの厳格なアプローチのみが必要です。チェックインできますがobject.managedObjectContextdidSave:それはお勧めできません。DidSave 通知のハンドラーをインストールすることをお勧めします...

id observer = [[NSNotificationCenter defaultCenter]
    addObserverForName:NSManagedObjectContextDidSaveNotification
                object:rootContext
                 queue:nil
            usingBlock:^(NSNotification *note) {
    for (NSManagedObject *mo in rootContext.registeredObjects) {
        [rootContext refreshObject:mo mergeChanges:NO];
    }
}];

これにより、おそらくあなたが望むものが得られることがわかります...ただし、実際に何を達成しようとしているのかを判断できるのはあなただけです.

于 2013-04-20T18:21:12.433 に答える
3

上記の手順は、Core Data で実行する一般的なタスクです。また、副作用については Apple のCore Data Programming Guide: Object Lifetime Managementで明確に文書化されています。

管理対象オブジェクト間に関係がある場合、各オブジェクトは関連するオブジェクトへの強力な参照を維持します。これにより、強い参照サイクルが発生する可能性があります。参照サイクルが壊れていることを確認するには、オブジェクトを使い終わったときに、管理対象オブジェクト コンテキスト メソッド refreshObject:mergeChanges: を使用して、それをフォルトに変えることができます。

オブジェクトは、それらがフォルトではなく、ライブ インスタンスである場合にのみ、相互への強い参照を維持しますNSManagedObject。ネストされたコンテキストを使用して、オブジェクトをメイン コンテキストに保存すると、それらの変更がルート コンテキストに反映される必要があります。ただし、ルート コンテキストでフェッチしない限り、保持サイクル作成されません。メイン コンテキストの保存が完了したら、これらのオブジェクトを更新するだけで十分です。

一般的なメモリフットプリントについて:

割り当てが手に負えないほど大きくなっていると感じた場合は、コードを構造化して、大量のオブジェクトに障害を発生させるタスクを別のコンテキストで実行し、タスクが完了したら破棄するようにすることができます。

また、取り消しマネージャーを使用している場合は、

コンテキストに関連付けられた元に戻すマネージャーは、変更された管理対象オブジェクトへの強力な参照を保持します。デフォルトでは、OS X では、コンテキストの取り消しマネージャーが無制限の取り消し/やり直しスタックを保持します。アプリケーションのメモリ フットプリントを制限するには、必要に応じてコンテキストの取り消しスタックを (removeAllActions を使用して) スクラブする必要があります。コンテキストの元に戻すマネージャーへの強い参照を維持しない限り、そのコンテキストで割り当てが解除されます。

更新 #1:

これをテストするために特別に作成された割り当てツールとコードを試した後、ルート コンテキストがメモリを解放しないことを確認できました。これはフレームワークのバグであるか、意図的に意図されたものです。同じ問題を説明している投稿が見つかりました。

[context reset]後で呼び出す[context save:]と、必要に応じてメモリが解放されました。また、保存する前に、ルートコンテキストには、子コンテキストを介して挿入したすべてのオブジェクトがセットに含まれていることに気付きました[context insertedObjects]。それらを反復して実行すると[context refreshObject:mergeChanges:NO]、オブジェクトが再フォールトしました。

したがって、回避策はほとんどないようですが、これがバグであり、今後のリリースで修正されるのか、それとも仕様のままなのかはわかりません。

于 2013-04-15T05:55:09.123 に答える