1

我有一个基于文档的 Cocoa 应用程序,我想为其实现状态恢复。具体来说,我的 NSDocument 子类包含一个需要保留其某些属性的控制器对象。问题是其中一个窗口控制器在控制器恢复之前将自己注册为控制器的观察者。因此,当控制器恢复时,应用程序会抛出异常,因为它试图在默认控制器已被观察时释放它。

DataController 类的实例 0x600000001ba0 已被释放,而键值观察者仍向其注册。

我创建了一个重现问题的最小示例。结构如下:

RestorationExample
|-> Document.h
|-> Document.m // Custom subclass of NSDocument, has DataController property
|-> DataController.h
|-> DataController.m // Controller that needs to be restored
|-> MainWindowController.h
|-> MainWindowController.m // Observes self.document.dataController
|-> MainWindow.xib // Window controlled by MainWindowController

您可以从此处下载示例项目,以便更轻松地设置所有内容:https ://github.com/mpflanzer/restoration_example

简单地Document创建MainWindowController

- (void)makeWindowControllers {
    [self addWindowController:[[MainWindowController alloc] initWithWindowNibName:@"MainWindow"]];
}

并编码/恢复其 dataController 属性

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
    NSLog(@"Document::encode");
    [super encodeRestorableStateWithCoder:coder];
    [coder encodeObject:self.dataController forKey:@"dataController"];
}

- (void)restoreStateWithCoder:(NSCoder *)coder
{
    NSLog(@"Document::restore");
    [super restoreStateWithCoder:coder];
    self.dataController = [coder decodeObjectForKey:@"dataController"];
}

在方法MainWindowController中将自己注册为观察者并在. 那是如何正确地做到这一点?还是应该使用其他功能来添加或删除观察者?windowDidLoadwindowWillClose

- (void)windowDidLoad {
    NSLog(@"MainWindowController::windowDidLoad");
    [super windowDidLoad];
    [((Document*)self.document).dataController addObserver:self forKeyPath:@"dataOffset" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
}

- (void)windowWillClose:(NSNotification *)notification
{
    [((Document*)self.document).dataController removeObserver:self forKeyPath:@"dataOffset"];
}

由于应用程序已经失败,目前DataController没有做任何特别的事情。后来它显然必须实施initWithCoder以恢复其属性。

当我运行示例(并触发状态恢复)时,我得到以下日志输出:

2017-04-19 18:57:54.846297+0100 RestorationExample[16829:1225236] Document::init
2017-04-19 18:57:54.846440+0100 RestorationExample[16829:1225236] DataController::init
2017-04-19 18:57:54.887825+0100 RestorationExample[16829:1225236] Document::readFromData
2017-04-19 18:57:54.953205+0100 RestorationExample[16829:1225236] MainWindowController::windowDidLoad
2017-04-19 18:57:54.953386+0100 RestorationExample[16829:1225236] Change data offset: 0
2017-04-19 18:57:54.957346+0100 RestorationExample[16829:1225236] Document::restore
2017-04-19 18:57:54.958678+0100 RestorationExample[16829:1225236] Ignoring exception raised in void _NSPersistentUIExecuteDispatchedBlock(void (^)(void)): An instance 0x600000001c20 of class DataController was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x60000002ae00> (
<NSKeyValueObservance 0x600000056050: Observer: 0x600000088a70, Key path: dataOffset, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x60000005e9c0>
)
2017-04-19 18:57:59.592119+0100 RestorationExample[16829:1225236] Document::encode

存在的问题是,makeWindowControllers因此在恢复windowDidLoad之前被调用。Document鉴于该顺序,MainWindowController将注册为观察者,然后Document才会尝试将其dataController属性恢复到已保存状态。

我如何实施状态恢复有什么问题吗?或者以我添加/删除观察者的方式?我本来希望Document先恢复,然后才makeWindowControllers调用。

4

0 回答 0