NSDocument doesn't autosave last changes

I had noticed an unsettling behaviour about NSDocument some years ago and created FB7392851, but the feedback didn't go forward, so I just updated it and hopefully here or there someone can explain what's going on.

When running a simple document-based app with a text view, what I type before closing the app may be discarded without notice. To reproduce it, you can use the code below, then:

  1. Type "asdf" in the text view.
  2. Wait until the Xcode console logs "saving". You can trigger it by switching to another app and back again.
  3. Type something else in the text view, such as "asdf" on a new line.
  4. Quit the app.
  5. Relaunch the app. The second line has been discarded.

Am I doing something wrong or is this a bug? Is there a workaround?

class ViewController: NSViewController {

    @IBOutlet var textView: NSTextView!

}

class Document: NSDocument {

    private(set) var text = ""

    override class var autosavesInPlace: Bool {
        return true
    }

    override func makeWindowControllers() {
        let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
        let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as! NSWindowController
        (windowController.contentViewController as? ViewController)?.textView.string = text
        self.addWindowController(windowController)
    }

    override func data(ofType typeName: String) throws -> Data {
        Swift.print("saving")
        text = (windowControllers.first?.contentViewController as? ViewController)?.textView.string ?? ""
        return Data(text.utf8)
    }

    override func read(from data: Data, ofType typeName: String) throws {
        text = String(decoding: data, as: UTF8.self)
        (windowControllers.first?.contentViewController as? ViewController)?.textView.string = text
    }

}
Answered by DTS Engineer in 853894022

What you described is an as-designed behavior. Basically, the autosave feature automatically saves the document (if there are unsaved changes) every once a while, or when some events happen (such as when the app is activated or deactivated). If you made some changes on the document, and kill the app before autosave is triggered, the changes will be lost.

On macOS, a user can kill an app in different ways – They can run the Quit app menu by pressing CMD+Q, run the Finder > Force Quit... menu, or execute kill -9 from Terminal.app, or even turn off the power.

To avoid losing the unsaved changes, folks typically implement applicationShouldTerminate(_:), which is triggered when the user runs the Quit app menu (CMD+Q), and save the document from there.

Force Quit and kill -9 are meant to quit the app immediately, and so there is no good way to run your code when the events happen. I won't worry too much though, because when a user quits an app in those ways, they are clear that they'd risk data loss.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

@Nickkk The issue was been tracked, the response provided to your Feedback report FB7392851 was for your to please file a new feedback report if the issue persists and attach diagnostic information.

Given that you're still able to reproduce the issue, could you please post the new Feedback number. That would help with investigating and tracking the issue.

Thanks

I just created another one, FB17662376. Yes, in the previous one I was asked to create a new one, but without giving any reason. To be honest, it can be quite irritating when you guys think that a bug has been solved and ask to verify and if it's not solved, to open a new feedback. It happens regularly. It's still the same exact issue, so why would one have to create a completely new feedback with the same data as before? It comes across as time-consuming bureaucracy.

What you described is an as-designed behavior. Basically, the autosave feature automatically saves the document (if there are unsaved changes) every once a while, or when some events happen (such as when the app is activated or deactivated). If you made some changes on the document, and kill the app before autosave is triggered, the changes will be lost.

On macOS, a user can kill an app in different ways – They can run the Quit app menu by pressing CMD+Q, run the Finder > Force Quit... menu, or execute kill -9 from Terminal.app, or even turn off the power.

To avoid losing the unsaved changes, folks typically implement applicationShouldTerminate(_:), which is triggered when the user runs the Quit app menu (CMD+Q), and save the document from there.

Force Quit and kill -9 are meant to quit the app immediately, and so there is no good way to run your code when the events happen. I won't worry too much though, because when a user quits an app in those ways, they are clear that they'd risk data loss.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

On macOS, a user can kill an app in different ways

I don't expect the document changes to be saved when the user intentionally kills the app, but I would expect that when the app is quit normally via the main menu or keyboard shortcut Command-Q, the pending changes would be saved. Are such changes really intended to be discarded? After all, if there is an open document that has never been saved and I try to close it, I'm asked if I want to save or delete it. Why would the last changes not be autosaved when I gracefully quit the app? It seems weird that changes are saved periodically, but not at the very end of the lifetime of the app.

If I'm really supposed to autosave all documents manually in applicationShouldTerminate, which API should I call? Again, it seems weird that this is taken care of automatically until before quitting the app, and then I have to manually track which documents are saved and when everything's saved, quit the app. It would seem to me like boilerplate code that every document-based app would automatically want.

I tried this implementation to autosave changes before quitting the app, but it doesn't work since NSDocument.hasUnautosavedChanges is unexpectedly false. I now get the impression that perhaps a document would be saved automatically before quitting, but there is an issue with how NSTextView marks a document as edited. I would appreciate any insight into this issue.

func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
    let edited = documents.filter({ $0.hasUnautosavedChanges })
    if edited.isEmpty {
        return .terminateNow
    }
    var remaining = edited.count
    for document in edited {
        document.autosave(withImplicitCancellability: false) { [self] error in
            if let error = error {
                presentError(error)
            } else {
                remaining -= 1
                if remaining == 0 {
                    NSApp.reply(toApplicationShouldTerminate: true)
                }
            }
        }
    }
    return .terminateLater
}
NSDocument doesn't autosave last changes
 
 
Q