NSDistributedNotificationsを使用してプロセス間でコアデータストアを共有するにはどうすればよいですか?
質問
背景
指定された推奨事項を実装しようとしていますが、問題が発生しています。
マイゴール
ヘルパーアプリケーションとUIの2つのプロセスがあります。どちらも単一のデータストアを共有します。ヘルパーアプリが新しいデータをストアに保存したときに、UIがNSManagedObjectContextを更新するようにします。
現在のプログラムフロー
-
ヘルパーアプリプロセスはデータをストアに書き込みます。
-
ヘルパーアプリで、NSManagedObjectContextDidSaveNotification通知をリッスンします。
-
コンテキストが保存されると、URI表現とNSArchiverを使用して、挿入、削除、および更新されたオブジェクトをエンコードします。
-
このエンコードされた辞書をuserInfoとしてNSNotificationをNSDistributedNotificationCenterに送信します。
-
UIプロセスは保存通知をリッスンしています。通知を受信すると、NSUnarchiverを使用してuserInfoをアーカイブ解除します。
-
指定されたURIから更新/挿入/削除されたすべてのオブジェクトを検索し、それらをNSManagedObjectsに置き換えます。
-
更新/挿入/削除されたオブジェクトでNSNotificationを構築します。
-
mergeChangesFromContextDidSaveNotification:UIプロセスの管理対象オブジェクトコンテキストで呼び出し、前の手順で作成したNSNotificationを渡します。
問題
挿入されたオブジェクトはUI Managed Object Contextにフォールトされ、UIに表示されます。問題は更新されたオブジェクトにあります。更新されません。
試したこと
-
最も明白なことは、 保存通知を渡す ヘルパーアプリプロセスから UIプロセス。簡単ですね。うーん、ダメ。 分散通知はしません userInfoとしてそれを許可します 辞書が右側にありません フォーマット。それが私がすべてをしている理由です NSArchivingスタッフ。
-
電話してみた refreshObject:mergeChanges:YES on 更新されるNSManagedObjects しかし、これは何も持っていないようです 効果。
-
私は mergeChangesFromContextDidSaveNotification: メインスレッドのセレクターと 現在のスレッド。どちらも 結果に影響します。
-
使用してみました mergeChangesFromContextDidSaveNotification: 前のスレッド間、 コースははるかに簡単で、うまくいきました 完全に。しかし、私はこれと同じが必要です プロセス間の機能。
代替案
ここに何か足りないのですか?私はこれを必要以上に複雑にしていると感じていますが、ドキュメントを数回読んでこれに数日を費やした後、他の方法でMOCを更新することはできませんUI。
これを行うよりエレガントな方法はありますか?それとも、コードのどこかで愚かな間違いを犯していますか?
コード
できるだけ読みやすくしようとしましたが、それでも混乱です。申し訳ありません。
ヘルパーアプリコード
-(void)workerThreadObjectContextDidSave:(NSNotification *)saveNotification {
NSMutableDictionary *savedObjectsEncodedURIs = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[saveNotification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// This is the set of updated/inserted/deleted NSManagedObjects.
NSSet *thisSavedObjectSet = [[saveNotification userInfo] objectForKey:thisSavedObjectKey];
NSMutableSet *thisSavedObjectSetEncoded = [NSMutableSet set];
for(id thisSavedObject in [thisSavedObjectSet allObjects]) {
// Construct a set of URIs that will be encoded as NSData
NSURL *thisSavedObjectURI = [[(NSManagedObject *)thisSavedObject objectID] URIRepresentation];
[thisSavedObjectSetEncoded addObject:thisSavedObjectURI];
}
// Archive the set of URIs.
[savedObjectsEncodedURIs setObject:[NSArchiver archivedDataWithRootObject:thisSavedObjectSetEncoded] forKey:thisSavedObjectKey];
}
if ([[savedObjectsEncodedURIs allValues] count] > 0) {
// Tell UI process there are new objects that need merging into it's MOC
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.synapticmishap.lapsus.save" object:@"HelperApp" userInfo:(NSDictionary *)savedObjectsEncodedURIs];
}
}
UIコード
-(void)mergeSavesIntoMOC:(NSNotification *)notification {
NSDictionary *objectsToRefresh = [notification userInfo];
NSMutableDictionary *notificationUserInfo = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[notification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// Iterate through all the URIs in the decoded set. For each URI, get the NSManagedObject and add it to a set.
NSSet *thisSavedObjectSetDecoded = [NSUnarchiver unarchiveObjectWithData:[[notification userInfo] objectForKey:thisSavedObjectKey]];
NSMutableSet *savedManagedObjectSet = [NSMutableSet set];
for(NSURL *thisSavedObjectURI in thisSavedObjectSetDecoded) {
NSManagedObject *thisSavedManagedObject = [managedObjectContext objectWithID:[persistentStoreCoordinator managedObjectIDForURIRepresentation:thisSavedObjectURI]];
[savedManagedObjectSet addObject:thisSavedManagedObject];
// If the object is to be updated, refresh the object and merge in changes.
// This doesn't work!
if ([thisSavedObjectKey isEqualToString:NSUpdatedObjectsKey]) {
[managedObjectContext refreshObject:thisSavedManagedObject mergeChanges:YES];
[managedObjectContext save:nil];
}
}
[notificationUserInfo setObject:savedManagedObjectSet forKey:thisSavedObjectKey];
}
// Build a notification suitable for merging changes into MOC.
NSNotification *saveNotification = [NSNotification notificationWithName:@"" object:nil userInfo:(NSDictionary *)notificationUserInfo];
[managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:YES];
}
解決
探しているのは-(void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag信じています。
これにより、永続ストア内の情報でオブジェクトが更新され、必要に応じて変更がマージされます。
他のヒント
でメソッドを使用しました
http://www.mlsite.net/blog/?p=518
すべてのオブジェクトは正しくフォールトされますが、フォールトはキャッシュにフェッチされるため、更新はまだ行われません
やらなければならなかった [moc stalenessInterval = 0];
そして、それはついに関係を持ち、うまくいきました。
マイクの提案に従い、ストアファイルの変更を監視します。
これは最も効率的ではないかもしれませんが、ストアに変更がある場合に、2番目のプロセスから-[NSManagedObjectContext reset]
を使用して成功しました。私の場合、コードはかなり線形です—リセット後、一部のデータのフェッチリクエストを実行するだけです。バインディングと複雑なUIでこれがどのように機能するかはわかりませんが、通知が自動的に処理されない場合は手動で更新するために通知を投稿できる場合があります。
これまで取り組んできたiPhoneアプリでも同じ問題がありました。私の場合、ソリューションにはContextのstalenessIntervalを適切に無限小に設定する必要がありました(0.5秒など)。
これは、サンドボックスアプリを除いて機能します。ユーザー情報辞書を使用して通知を送信することはできません。代わりに、XPCやDOなどの他のIPCを検討してください。
補足的に、システムがビジー状態の場合、NSDustributedNotificationCenterの使用は必ずしも100%ではありません。
管理対象オブジェクトコンテキストのstalenessIntervalの設定は機能します。私の場合、プロセスではなく複数のスレッドが関係しています。