背景
- 使用核心数据的 Cocoa 应用程序 两个进程 - 守护进程和主 UI
- 守护进程不断写入数据存储
- UI 进程从同一数据存储中读取
- UI 中 NSOutlineView 中的列绑定到 NSTreeController
- NSTreeControllers managedObjectContext 绑定到 Application 的 key path 为 delegate.interpretedMOC
- NSTreeControllers 实体设置为 TrainingGroup(NSManagedObject 子类称为 JGTrainingGroup)
我想要的是
当 UI 被激活时,大纲视图应该使用守护程序插入的最新数据进行更新。
问题
主线程方法
我获取所有我感兴趣的实体,然后迭代它们,执行 refreshObject:mergeChanges:YES。这工作正常 - 项目正确刷新。但是,这一切都在主线程上运行,因此 UI 在刷新时会锁定 10-20 秒。好的,所以让我们将这些刷新转移到在后台运行的 NSOperations。
NSOperation 多线程方法
一旦我将 refreshObject:mergeChanges: 调用移动到 NSOperation 中,刷新就不再起作用。当我添加日志消息时,很明显新对象是由 NSOperation 子类加载并刷新的。似乎无论我做什么, NSOutlineView 都不会刷新。
我试过的
我已经搞砸了 2 天,并尝试了我能想到的一切。
- 将 objectIDs 传递给 NSOperation 以刷新而不是实体名称。
- 在不同点重置解释的MOC - 在数据刷新之后和大纲视图重新加载之前。
- 我将 NSOutlineView 子类化。我丢弃了我的子类并将视图重新设置为 NSOutlineView 的一个实例,以防万一这里发生任何有趣的事情。
- 在重新加载 NSOutlineView 数据之前添加了对 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
我刚刚发现了这个问题。在发布我的之前,我无法理解我是如何错过它的,但总结是:Core Data 并不是为了做我正在做的事情而设计的。只有一个进程应该使用数据存储。
NSManagedObjectContext 和 NSArrayController 重置/刷新问题
但是,在我的应用程序的不同区域中,我有两个进程共享一个数据存储,其中一个具有只读访问权限,这似乎工作正常。另外,我关于这个主题的最后一个问题的答案都没有提到 Core Data 不支持这一点。
I'm going to re-architect my app so that only one process writes to the data store at any one time. I'm still skeptical that this will solve my problem though. It looks to me more like an NSOutlineView refreshing problem - the objects are created in the context, it's just the outline view doesn't pick them up.