【发布时间】:2010-06-06 13:49:08
【问题描述】:
背景
- Cocoa app 使用核心数据二 进程 - 守护进程和主 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 以进行刷新,而不是实体名称。
- 在不同点重置解释型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 and NSArrayController reset/refresh problem
但是,在我的应用程序的不同区域,我有两个进程共享一个数据存储,其中一个具有只读访问权限,这似乎工作正常。另外,我的last question on this topic 的答案都没有提到 Core Data 不支持这一点。
我将重新设计我的应用程序,以便在任何时候只有一个进程写入数据存储。我仍然怀疑这会解决我的问题。在我看来,它更像是一个 NSOutlineView 刷新问题——对象是在上下文中创建的,只是大纲视图没有拾取它们。
【问题讨论】:
标签: cocoa core-data nsoperation nsoutlineview nstreecontroller