基于文档的应用程序无法恢复具有非文件URL的文档

13

我有一个基于NSDocument的应用程序,其中包括一个NSDocumentController子类。 我的NSDocument可以处理文件URL和使用Web服务的自定义方案的URL。

我使用自定义代码处理许多加载和保存操作,包括-saveToURL:ofType:forSaveOperation:completionHandler:+autosavesInPlace 返回YES

我遇到的问题是:具有自定义URL方案的文档在启动时无法恢复。 具有文件URL方案的文档可以恢复 - 包括保存在文件中的常规文档和自动保存的未命名文档。

在打开基于服务器的文档并退出应用程序后,重新启动时似乎没有调用任何NSDocument方法。 特别是,四个初始化方法之一都没有被调用:

  • -init
  • -initWithContentsOfURL:ofType:error:
  • -initForURL:withContentsOfURL:ofType:error:
  • -initWithType:error:

NSDocumentController方法-reopenDocumentForURL:withContentsOfURL:display:completionHandler: 也没有被调用。

文档的可恢复状态是如何编码的?何时编码?何时解码?

2个回答

14

NSDocument负责在-encodeRestorableStateWithCoder:中对其可恢复状态进行编码,而NSDocumentController负责解码文档的可恢复状态并使用+restoreWindowWithIdentifier:state:completionHandler:重新打开文档。请参考NSDocumentRestoration.h中的有用注释。

当NSDocument对URL进行编码时,它似乎使用NSURL的书签方法。问题是这些方法仅适用于文件系统URL。 (非文件URL可能会被编码,但不会正确解码。)

要解决此问题,请覆盖使用自定义方案的NSDocument实例的编码,以及这些文档的解码。

NSDocument子类:

- (void) encodeRestorableStateWithCoder:(NSCoder *) coder {
    if ([self.fileURL.scheme isEqualToString:@"customscheme"])
        [coder encodeObject:self.fileURL forKey:@"MyDocumentAutoreopenURL"];
    else
        [super encodeRestorableStateWithCoder:coder];
}

NSDocumentController子类:

+ (void) restoreWindowWithIdentifier:(NSString *) identifier
                               state:(NSCoder *) state
                   completionHandler:(void (^)(NSWindow *, NSError *)) completionHandler {

    NSURL *autoreopenURL = [state decodeObjectForKey:@"MyDocumentAutoreopenURL"];
    if (autoreopenURL) {
        [[self sharedDocumentController]
         reopenDocumentForURL:autoreopenURL
         withContentsOfURL:autoreopenURL
         display:NO
         completionHandler:^(NSDocument *document, BOOL documentWasAlreadyOpen, NSError *error) {

             NSWindow *resultWindow = nil;
             if (!documentWasAlreadyOpen) {

                 if (![[document windowControllers] count])
                     [document makeWindowControllers];

                 if (1 == document.windowControllers.count)
                     resultWindow = [[document.windowControllers objectAtIndex:0] window];
                 else {
                     for (NSWindowController *wc in document.windowControllers)
                         if ([wc.window.identifier isEqual:identifier]) {
                             resultWindow = wc.window;
                             break;
                         }
                 }
             }
             completionHandler(resultWindow, error);
         }
         ];
    } else
        [super restoreWindowWithIdentifier:identifier
                                     state:state
                         completionHandler:completionHandler];
}

完成处理程序的行为遵循苹果在NSDocumentRestoration.h中的方法注释,并且应该与super的行为大致相同。


5
窗口状态编码可以通过NSWindow上的两种方法启用。调用setRestorable:将窗口标记为可以在重新启动时保存和恢复的窗口,然后调用setRestorationClass:让您指定一个类来处理重新创建该保存的窗口。
默认情况下,AppKit将NSDocumentController设置为由NSDocument对象控制的窗口的还原类。实际的还原是通过调用+restoreWindowWithIdentifier:state:completionHandler:方法完成的,该方法由NSWindowRestoration协议定义。对于文档,NSDocumentController实现该方法,并根据传递到该方法中的NSCoder实例中编码的状态重新创建NSDocument对象。
因此,理论上,如果您要子类化NSDocumentController并覆盖该方法,那么这将为您提供一个机会,以从状态还原机制保存的文档中进行还原。但是,据我所知,NSDocumentController用于存储状态的键没有在任何地方记录,因此我认为没有可靠的方式可以直接从NSDocumentController自己存储的状态进行还原。
为了支持此功能,您可能需要自己对文档的整个状态进行编码,通过在编码的NSWindow上实现-encodeRestorableStateWithCoder:方法和/或为窗口实现window:willEncodeRestorableState:委托方法。这两种方法都会向您传递一个NSCoder实例,您可以使用该实例来编码您的状态。这就是您将编码自定义方案URL以及任何其他需要保存/恢复状态的关联数据的地方。然后您将在restoreWindowWithIdentifier:state:completionHandler:方法中解码该状态。
由于您将有一些具有常规文件URL的文档和一些具有自定义URL的文档,因此我建议创建一个负责解码文档状态的单独类,并仅将其设置为具有自定义URL的文档的还原类,让NSDocumentController为具有文件URL的文档为您处理。

2
这对于入门非常有帮助。事实证明,故事更加复杂:实际上是NSDocument而不是NSWindow负责在其自己的-encodeRestorableStateWithCoder:实现中编码文档状态(编号ID、URL、是否有最近更改、类型)。NSDocumentController使用该状态信息来恢复文档,然后调用-makeWindowControllers和NSApplication(或NSApplication的完成处理程序?)来恢复窗口。此外,在使用NSDocumentController的自定义子类时,AppKit将类设置为“restorationClass”。 - paulmelnikow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接