Search code examples
macoscocoa-bindingsnsdocument

NSPersistentDocument, Storyboard and NSArrayController = crash on close


Problem: closing the document window produces the following exception:

An instance 0x600000140630 of class SimpleApp.Document was deallocated while key value 
observers were still registered with it. Current observation info:  <NSKeyValueObservationInfo 0x6100000424f0> …

<NSKeyValueObservance 0x6100000c2f40: Observer: 0x6280000c7a10, Key path: managedObjectContext, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x6100000429a0>

and

-[NSAutoreleasePool drain]: This pool has already been drained, do not release it (double release).

Here’ what I’ve done:

Created a new Xcode Project using:

  • Use Storyboards
  • Create Document-Based Application
  • Use Core Data

In Document.xcdatamodeld

  • created an Entity (Part) with 2 attributes, width and length

In Main.storyboard,

  • dragged a table view (view based), 2 buttons (“Add” and “Remove”) and an ArrayController to the view controller

  • ArrayController mode set to Entity

Bindings:

  • tableView content: ArrayController arrangedObjects, Selection Indexes: ArrayController selectionIndexes
  • the 2 table view cells were bound to Table Cell View, keyPath: objectValue.width and objectValue.length

Now the first problem was to bind the ArrayController managedObjectContext. I needed a reference to the Document subclass. So I added

weak var document: Document? {
    didSet {
        print("ViewController, document didSet")
    }
}

in ViewController and bound the ArrayController managed object context to it (document.managedObjectContext).

After trying, without success, to set this var in various places (ViewController.viewDidLoad() , in a NSWindowController subclass's windowDidLoad(), NSDocumentController.sharedDocumentController().documentForWindow(self.window!) was always nil.

I made it work using the following in Document.makeWindowControllers()

if let viewController = windowController.contentViewController {
     viewController.setValue(self, forKey: "document")
}

So, now I can create a new document, and save it, open a saved document but as soon as I close the window I get the previous exception. I added deinit methods to the view controller and the document and the exception occurs after Document.deinit is called but before ViewController.deinit. So it looks like the array controller is still observing the Document managedObjectContext which no longer exists.

Maybe I’m missing something obvious here, but I can’t find a simple example on how to use storyboards, array controller and document together. BTW, I also tried without Core Data and got the same exception.

UPDATED

I've made a github project https://github.com/Miyan0/SimpleApp.git

The steps to reproduce the crash:

  • create a new document
  • save it
  • reopen the document
  • make any modifications
  • click the closebox (without saving)
  • crash

Solution

  • Looks like the only way to solve this is by changing the document's reference in ViewController from weak to strong.

    I was afraid it may cause a retain cycle but it doesn't (according to the print statements and deinit methods for ViewController and Document). I tried to cover all possibles cases and even if sometimes the document isn't deallocated after closing it's window, it will eventually be deallocated when a new document is created or another one is modified.