バックグラウンド
- コア データを使用する Cocoa アプリ 2 つのプロセス - デーモンとメイン UI
- デーモンが常にデータ ストアに書き込みを行っている
- UI プロセスが同じデータ ストアから読み取る
- NSTreeController にバインドされた UI の NSOutlineView の列
- NSTreeControllers managedObjectContext は、delegate.interpretedMOC のキー パスを持つアプリケーションにバインドされます。
- NSTreeControllers エンティティは TrainingGroup に設定されます (NSManagedObject サブクラスは JGTrainingGroup と呼ばれます)
私が欲しいもの
UI がアクティブになると、デーモンによって挿入された最新のデータでアウトライン ビューが更新されます。
問題
メインスレッドアプローチ
関心のあるすべてのエンティティをフェッチし、refreshObject:mergeChanges:YES を実行してそれらを反復処理します。これは問題なく動作します - アイテムは正しく更新されます。ただし、これはすべてメイン スレッドで実行されるため、更新中に UI が 10 ~ 20 秒間ロックされます。では、これらの更新をバックグラウンドで実行される NSOperations に移動しましょう。
NSOperation マルチスレッド アプローチ
refreshObject:mergeChanges: 呼び出しを NSOperation に移動するとすぐに、更新が機能しなくなります。ログ メッセージを追加すると、新しいオブジェクトが NSOperation サブクラスによって読み込まれ、更新されることは明らかです。私が何をしても、 NSOutlineView が更新されないようです。
私が試したこと
私はこれを2日間しっかりといじり、考えられるすべてを試しました。
- エンティティ名の代わりにオブジェクト ID を NSOperation に渡して更新します。
- データの更新後、アウトライン ビューの再読み込み前など、さまざまな時点で解釈された MOC をリセットします。
- NSOutlineView をサブクラス化しました。ここでおかしなことが起こった場合に備えて、サブクラスを破棄し、ビューを NSOutlineView のインスタンスに戻しました。
- NSOutlineView データをリロードする前に、rearrangeObjects 呼び出しを NSTreeController に追加しました。
- 使用していたすべての管理対象オブジェクト コンテキストで、失効間隔を 0 に設定したことを確認しました。
この問題は、メモリ内のコア データ オブジェクトのキャッシュに何らかの形で関連していると感じています。しかし、これを機能させる方法についてのアイデアはすべて使い果たしました。
なぜこれが機能しないのかについて光を当てることができる人には、永遠に感謝します.
コード
メインスレッドアプローチ
// In App Delegate
-(void)applicationDidBecomeActive:(NSNotification *)notification {
// Delay to allow time for the daemon to save
[self performSelector:@selector(refreshTrainingEntriesAndGroups) withObject:nil afterDelay:3];
}
-(void)refreshTrainingEntriesAndGroups {
NSSet *allTrainingGroups = [[[NSApp delegate] interpretedMOC] fetchAllObjectsForEntityName:kTrainingGroup];
for(JGTrainingGroup *thisTrainingGroup in allTrainingGroups)
[interpretedMOC refreshObject:thisTrainingGroup mergeChanges:YES];
NSError *saveError = nil;
[interpretedMOC save:&saveError];
[windowController performSelectorOnMainThread:@selector(refreshTrainingView) withObject:nil waitUntilDone:YES];
}
// In window controller class
-(void)refreshTrainingView {
[trainingViewTreeController rearrangeObjects]; // Didn't really expect this to have any effect. And it didn't.
[trainingView reloadData];
}
NSOperation マルチスレッド アプローチ
// In App Delegate (just the changed method)
-(void)refreshTrainingEntriesAndGroups {
JGRefreshEntityOperation *trainingGroupRefresh = [[JGRefreshEntityOperation alloc] initWithEntityName:kTrainingGroup];
NSOperationQueue *refreshQueue = [[NSOperationQueue alloc] init];
[refreshQueue setMaxConcurrentOperationCount:1];
[refreshQueue addOperation:trainingGroupRefresh];
while ([[refreshQueue operations] count] > 0) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
// At this point if we do a fetch of all training groups, it's got the new objects included. But they don't show up in the outline view.
[windowController performSelectorOnMainThread:@selector(refreshTrainingView) withObject:nil waitUntilDone:YES];
}
// JGRefreshEntityOperation.m
@implementation JGRefreshEntityOperation
@synthesize started;
@synthesize executing;
@synthesize paused;
@synthesize finished;
-(void)main {
[self startOperation];
NSSet *allEntities = [imoc fetchAllObjectsForEntityName:entityName];
for(id thisEntity in allEntities)
[imoc refreshObject:thisEntity mergeChanges:YES];
[self finishOperation];
}
-(void)startOperation {
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isStarted"];
[self setStarted:YES];
[self setExecuting:YES];
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isStarted"];
imoc = [[NSManagedObjectContext alloc] init];
[imoc setStalenessInterval:0];
[imoc setUndoManager:nil];
[imoc setPersistentStoreCoordinator:[[NSApp delegate] interpretedPSC]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:imoc];
}
-(void)finishOperation {
saveError = nil;
[imoc save:&saveError];
if (saveError) {
NSLog(@"Error saving. %@", saveError);
}
imoc = nil;
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
[self setExecuting:NO];
[self setFinished:YES];
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
-(void)mergeChanges:(NSNotification *)notification {
NSManagedObjectContext *mainContext = [[NSApp delegate] interpretedMOC];
[mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}
-(id)initWithEntityName:(NSString *)entityName_ {
[super init];
[self setStarted:false];
[self setExecuting:false];
[self setPaused:false];
[self setFinished:false];
[NSThread setThreadPriority:0.0];
entityName = entityName_;
return self;
}
@end
// JGRefreshEntityOperation.h
@interface JGRefreshEntityOperation : NSOperation {
NSString *entityName;
NSManagedObjectContext *imoc;
NSError *saveError;
BOOL started;
BOOL executing;
BOOL paused;
BOOL finished;
}
@property(readwrite, getter=isStarted) BOOL started;
@property(readwrite, getter=isPaused) BOOL paused;
@property(readwrite, getter=isExecuting) BOOL executing;
@property(readwrite, getter=isFinished) BOOL finished;
-(void)startOperation;
-(void)finishOperation;
-(id)initWithEntityName:(NSString *)entityName_;
-(void)mergeChanges:(NSNotification *)notification;
@end
更新 1
この質問を見つけました。私が投稿する前にどうしてそれを見逃したのか理解できませんが、要約すると、コアデータは私がしていることを行うように設計されていません. 1 つのプロセスのみがデータ ストアを使用する必要があります。
NSManagedObjectContext および NSArrayController のリセット/リフレッシュの問題
ただし、アプリケーションの別の領域では、データ ストアを共有する 2 つのプロセスがあり、1 つのプロセスが読み取り専用アクセスを持っていますが、これは正常に機能しているように見えました。さらに、このトピックに関する私の最後の質問に対する回答のどれも、これが Core Data でサポートされていないことを述べていませんでした。
一度に 1 つのプロセスだけがデータ ストアに書き込むように、アプリを再構築します。しかし、これが私の問題を解決するかどうかはまだ懐疑的です。私には NSOutlineView の更新の問題のように見えます-オブジェクトはコンテキストで作成され、アウトラインビューがそれらを拾わないだけです。