- какао приложения с использованием основных данных Двух процессов - демон и основного пользовательский интерфейс
- Daemon постоянно писать в хранилище данных
- Процесс пользовательского интерфейса читается с одних и тех же данных
- Столбцы в NSOutlineView в UI, привязанные к NSTreeController
- NSTreeControllers managedObjectContext связан Применение с ключом путь delegate.interpretedMOC
- NSTreeControllers объект устанавливается в TrainingGroup (NSManagedObject подкласс называется JGTrainingGroup)
То, что я хочу
When пользовательский интерфейс активируется, представление схемы должно обновляться с последними данными, вставленными демоном.
Проблема
Основной подход Thread
Принести все сущности Я заинтересован в, а затем перебрать их, делая refreshObject: mergeChanges: YES. Это работает нормально - элементы обновляются правильно. Тем не менее, все это работает на основном потоке, поэтому пользовательский интерфейс блокируется на 10-20 секунд, пока он обновляется. Отлично, поэтому давайте переместим эти обновления в NSOperations, которые работают в фоновом режиме. не
NSOperation Multithreaded подход
Как только я перенесу refreshObject: mergeChanges: позвонить в NSOperation, что обновление больше не работает. Когда я добавляю сообщения о регистрации, становится ясно, что новые объекты загружаются подклассом NSOperation и обновляются. Кажется, что независимо от того, что я делаю, NSOutlineView не будет обновляться.
То, что я пытался
Я возился с этим в течение 2-х дней твердых и попробовал все, что я могу думать.
- Передача идентификаторов объекта NSOperation для обновления вместо имени объекта.
- Сброс интерпретируемого MOC в разных точках - после обновления данных и до перезагрузки схемы.
- Я бы подклассифицировал NSOutlineView. Я отбросил свой подкласс и вернул представление в экземпляр NSOutlineView, на случай, если бы здесь были какие-то смешные события.
- Добавлен вызов перегруппировки для NSTreeController перед перезагрузкой данных NSOutlineView.
- Удостоверился, что я установил интервал молчания равным 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 Multithreaded подход
// 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
UPDATE 1
Я только что нашел этот вопрос. Я не могу понять, как я пропустил это, прежде чем я разместил свой, но сводка: Основные данные не были предназначены для того, чтобы делать то, что я делаю. Только один процесс должен использовать хранилище данных.
NSManagedObjectContext and NSArrayController reset/refresh problem
Однако в другой области моего приложения у меня есть два процесса обмена хранилище данных с одним Начитавшись только доступ, и это, казалось, прекрасно работать. Плюс ни один из ответов на мой last question on this topic не упоминал, что это не поддерживалось в Core Data.
Я собираюсь перестроить мое приложение так, чтобы только один процесс записывал в хранилище данных в любой момент времени. Я все еще скептически отношусь к тому, что это решит мою проблему. Мне кажется, что это больше напоминает проблему обновления NSOutlineView - объекты создаются в контексте, это просто представление схемы не подбирает их.