NSOutlineView не обновляется, когда объекты добавляются в контекст управляемого объекта из NSOperations - PullRequest
2 голосов
/ 06 июня 2010

Фон

  • Какао-приложение с использованием основных данных Два процессы - демон и основной интерфейс
  • Демон постоянно пишет в хранилище данных
  • Процесс пользовательского интерфейса читает из тех же данных магазин
  • Столбцы в NSOutlineView в пользовательском интерфейсе, привязанные к NSTreeController
  • NSTreeControllers managedObjectContext связан с Приложение с ключевым путем delegate.interpretedMOC
  • Для сущности NSTreeControllers задано значение TrainingGroup (подкласс NSManagedObject называется JGTrainingGroup)

Что я хочу

Когда пользовательский интерфейс активирован, представление структуры должно обновляться с использованием последних данных, вставленных демоном.

Проблема

Подход с главной нитью

Я выбираю все интересующие меня сущности, затем перебираю их, выполняя refreshObject: mergeChanges: YES. Это работает хорошо - элементы обновляются правильно. Однако все это выполняется в основном потоке, поэтому пользовательский интерфейс блокируется на 10-20 секунд, пока он обновляется. Хорошо, давайте перенесем эти обновления в NSOperations, которые работают в фоновом режиме.

NSOperation Многопоточный подход

Как только я переместил refreshObject: mergeChanges: call в NSOperation, обновление больше не работает. Когда я добавляю сообщения регистрации, ясно, что новые объекты загружаются подклассом NSOperation и обновляются. Кажется, что независимо от того, что я делаю, NSOutlineView не обновится.

Что я пробовал

Я два дня бездельничал и перепробовал все, что мог придумать.

  • Передача objectID в NSOperation для обновления вместо имени объекта.
  • Сброс интерпретированного MOC в различных точках - после обновления данных и до перезагрузки схематического представления.
  • Я бы подкласс NSOutlineView. Я отбросил свой подкласс и вернул представление в качестве экземпляра NSOutlineView, на случай, если здесь произойдут какие-то забавные события.
  • Добавлен вызов restrangeObjects в 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

// 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

Я только что нашел этот вопрос. Я не могу понять, как я пропустил это до того, как опубликовал свой, но резюме таково: Базовые данные не были предназначены для того, что я делаю Только один процесс должен использовать хранилище данных.

Проблема сброса / обновления NSManagedObjectContext и NSArrayController

Однако в другой области моего приложения у меня есть два процесса, которые совместно используют хранилище данных, и один из них имеет доступ только для чтения, и это, похоже, работает нормально. Плюс ни в одном из ответов на мой последний вопрос по этой теме не упоминалось, что это не поддерживается в Core Data.

Я собираюсь перестроить свое приложение, чтобы в один момент времени в хранилище данных записывался только один процесс. Я все еще скептически отношусь, что это решит мою проблему все же. Для меня это больше похоже на проблему с обновлением NSOutlineView - объекты создаются в контексте, просто контурное представление не воспринимает их.

1 Ответ

0 голосов
/ 09 июня 2010

Я закончил ре-архитектуру своего приложения. Я импортирую элементы только из одного процесса или другого сразу. И это работает отлично. Ура!

...