Document-based app doesn't restore documents with non-file URLs

cocoa, nsdocument, nsdocumentcontroller

Solution

NSDocument is responsible for encoding its restorable state in `-encodeRestorableStateWithCoder:`, and NSDocumentController is responsible for decoding documents' restorable state and reopening the documents in `+restoreWindowWithIdentifier:state:completionHandler:`. Refer to the helpful comments in `NSDocumentRestoration.h`.

When NSDocument encodes the URL, it appears to use the bookmark methods of NSURL. The problem is that these methods only work with file-system URLs. (It's possible non-file URLs will encode, but they will not properly decode.)

To fix the problem, override the encoding of NSDocument instances which use the custom scheme, and likewise, the decoding of those documents.

NSDocument subclass:

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

NSDocumentController subclass:

+ (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];
}

The behavior or the completion handler follows from Apple's method comment in NSDocumentRestoration.h and should be roughly the same as `super`'s.

Problem

I have an application based on NSDocument with an NSDocumentController subclass. My NSDocument works with both file URLs and URLs with a custom scheme which use a web service. I handle much of the loading and saving using custom code, including `-saveToURL:ofType:forSaveOperation:completionHandler:`. `+autosavesInPlace` returns `YES`. The problem I'm having: documents with the custom URL scheme aren't restored on startup. Documents with the file URL scheme are – both regular documents saved to files, and untitled documents which are autosaved. After leaving open server-based documents and quitting the app, no NSDocument methods appear to be called on restart. In particular, none of the four initializers is called: - –init - –initWithContentsOfURL:ofType:error: - –initForURL:withContentsOfURL:ofType:error: - –initWithType:error: The NSDocumentController method `-reopenDocumentForURL:withContentsOfURL:display:completionHandler:` is not called either. How and when are documents' restorable state encoded? How and when are they decoded?

Original source