macosexc-bad-accessnswindowprogrammatically-creatednsstatusbar

macOS statusBarApp without Storyboard create and close settingsWindow causing EXC_BAD_ACCESS


I have a MacOS cocoa statusBarApp without any Storyboard with main.swift file. The statusBarIcon shows up a Menu which presents a custom view with a button, which should open a settingsWindow - which it does. If I close the settingsWindow and reopen it and close it again, I got a EXC_BAD_ACCESS Error. It seems, that the window is deallocate but the reference is still present. I don't know how to fix this.

Edit the question like Willeke´s advice:

Thx, to your answer. Ok, hier is a minimal reproducible example:

create a new Xcode project, with storyboard and swift for macOS app. Under Project-Infos / General / Deployment Info: Delete the main entry to the storyboard. Then delete the storyboard file itself. Under Info set the "application is agent" flag to yes, so the app is statusBarApp only. then you only need the code below.

The Exception Breakpoint leads to this line:

settingsWindow = NSWindow(

To reproduce the error: start the app, click on statusItem, click on menuItem, a window opens, close the window, click again all first steps and reopen the window. sometimes that's the point of crash. sometimes a few more attempts of closing the window are necessary, but not more then three times.

main.swift

import Cocoa

let delegate = AppDelegate()
NSApplication.shared.delegate = delegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

AppDelegate.swift

import Cocoa

class AppDelegate: NSObject, NSApplicationDelegate {
    
    var settingsWindow: NSWindow!
    
    var statusItemMain: NSStatusItem?
    var menuMain = NSMenu()
    var menuItemMain = NSMenuItem()
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
                
        statusItemMain = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        let itemImage = NSImage(systemSymbolName: "power", accessibilityDescription: nil)
        itemImage?.isTemplate = true
        statusItemMain?.button?.image = itemImage
        
        menuItemMain.target = self
        menuItemMain.isEnabled = true
        menuItemMain.action = #selector(createWindow)
        menuMain.addItem(menuItemMain)
        
        menuMain.addItem(.separator())
                
        statusItemMain?.menu = menuMain
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }

    func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
        return true
    }
    
    @objc func createWindow() {
        settingsWindow = NSWindow(
                    contentRect: NSRect(x: 0, y: 0, width: 750, height: 500),
                    styleMask: [.miniaturizable, .closable, .resizable, .titled],
                    backing: .buffered, defer: false)
        settingsWindow.center()
        settingsWindow.title = "No Storyboard Window"
        settingsWindow.makeKeyAndOrderFront(nil)
        
        settingsWindow?.contentViewController = ViewController()
    }


}

ViewController.swift

import Cocoa

class ViewController: NSViewController {
    
    override func loadView() {
    self.view = NSView(frame: NSRect(x: 0, y: 0, width: 750, height: 500))
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }


}

Solution

  • NSWindow is released when it is closed. Before ARC this was a usefull feature. It can be switched off by setting the isReleasedWhenClosed property to false. But then the window stays in memory when it is closed because the settingsWindow property is holding on to it. Implement delegate method windowWillClose and set settingsWindow to nil so window is released.

    class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
    
        var settingsWindow: NSWindow!
    
        // other methods
    
        @objc func createWindow() {
            settingsWindow = NSWindow(
                        contentRect: NSRect(x: 0, y: 0, width: 750, height: 500),
                        styleMask: [.miniaturizable, .closable, .resizable, .titled],
                        backing: .buffered, defer: false)
            settingsWindow.isReleasedWhenClosed = false
            settingsWindow.delegate = self
            settingsWindow.center()
            settingsWindow.title = "No Storyboard Window"
            settingsWindow?.contentViewController = ViewController()
            settingsWindow.makeKeyAndOrderFront(nil)
        }
        
        func windowWillClose(_ notification: Notification) {
            settingsWindow = nil
        }
    
    }