当NSOperations中的对象添加到托管对象上下文时,NSOutlineView不会刷新

时间:2010-06-06 13:49:08

标签: cocoa core-data nsoperation nsoutlineview nstreecontroller

背景

  • 使用核心数据的Cocoa应用程序 进程 - 守护进程和主UI
  • 守护程序不断写入数据存储
  • UI进程从相同数据中读取 商店
  • UI中NSOutlineView中的列绑定到 一个NSTreeController
  • NSTreeControllers managedObjectContext绑定 应用程序的关键路径 delegate.interpretedMOC
  • NSTreeControllers实体设置为TrainingGroup(NSManagedObject子类称为JGTrainingGroup)

我想要什么

激活UI后,大纲视图应使用守护程序插入的最新数据进行更新。

问题

主线程方法

我获取我感兴趣的所有实体,然后迭代它们,执行refreshObject:mergeChanges:YES。这项工作正常 - 项目得到正确刷新。但是,这一切都在主线程上运行,因此UI在刷新时会锁定10-20秒。很好,所以让我们将这些刷新移动到在后台运行的NSOperations。

NSOperation多线程方法

一旦我移动refreshObject:mergeChanges:调用NSOperation,刷新就不再有效了。当我添加日志消息时,很明显新对象是由NSOperation子类加载并刷新的。似乎无论我做什么,NSOutlineView都不会刷新。

我尝试了什么

我已经搞砸了这2天了,并尝试了我能想到的一切。

  • 将objectID传递给NSOperation以刷新而不是实体名称。
  • 在数据刷新之后和大纲视图重新加载之前,在各个点重置interpretMOC。
  • 我将NSOutlineView子类化。我放弃了我的子类并将视图设置为NSOutlineView的一个实例,以防万一在这里有任何有趣的事情发生。
  • 在重新加载NSOutlineView数据之前,已向NSTreeController添加了rearrangeObjects调用。
  • 确保在我使用的所有托管对象上下文中将陈旧时间间隔设置为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

我刚发现这个问题。在我发布之前,我无法理解我是如何错过它的,但摘要是: Core Data不是为了做我正在做的事情。只有一个进程应该使用数据存储。

NSManagedObjectContext and NSArrayController reset/refresh problem

但是,在我的应用程序的不同区域中,我有两个共享数据存储的进程,其中一个具有只读访问权限,这似乎工作正常。另外,last question on this topic的答案都没有提到核心数据不支持。

我将重新构建我的应用程序,以便任何时候只有一个进程写入数据存储。我仍然怀疑这会解决我的问题。它看起来更像是一个NSOutlineView刷新问题 - 对象是在上下文中创建的,它只是大纲视图不会提取它们。

1 个答案:

答案 0 :(得分:0)

我最终重新构建了我的应用程序。我只是从一个进程或另一个进程中导入项目。而且效果很好。乌拉!