I have a NSDocument
based macOS app with Core Data which by its nature can only ever have one document open at a time. Therefore, when a new document is opened I close the currently open one.
All document related UI is in a separate window controller and everything works well.
But I also have a menu bar item that toggles a separate window which displays some information about the document. The UI is a simple NSTableView
bound to an NSArrayController
. The array controller's managagedObjectContext
property is set when the current document changes. This always leads to a crash with EXC_BAD_INSTRUCTION
.
To narrow down the issue I completely removed all bindings and any other manipulation of the array controller. The crash is gone.
I also created a new testArrayController
in code to see what happens there and sure enough I could reproduce the crash:
let testArrayController = NSArrayController()
var document: Document? {
didSet {
if document != nil {
testArrayController.managedObjectContext = document?.managedObjectContext
testArrayController.prepareContent() // <---- this causes the crash later on
} else {
testArrayController.managedObjectContext = nil
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
testArrayController.entityName = "MyEntity"
...
}
It seems that calling prepareContent()
somehow locks the array controller to a specific managedObjectContext and causes the crash when it is set to nil.
How can I safely "deactivate" an NSArrayController
, or change its managedObjectContext?
After lots of experimenting I think I found out that NSArrayController
produces a memory leak as soon as you call fetch(_:)
or preopareContent()
. It seems that it retains its managedObjectContext
and never releases it. Even though all other references to the controller have been released I could see the leaked instance in the memory debugger.
I worked around the issue by replacing bindings with a regular NSTableViewDataSource
implementation.