swiftmacoscocoaappkitnsapplication

Why does calling NSApplication.shared directly ignore the class specified by the Principal class in Info.plist?


I would like to use a custom subclass of NSApplication to create the application instance of my program. The object retrieved by NSApplication.shared or NSApp should be an instance of the Application class below:

class Application: NSApplication {
    // My own methods
}

I first tried to set Principal class in Info.plist from NSApplication to Application but I later learned I had to use $(PRODUCT_MODULE_NAME).Application instead. This worked, and instantiated my subclass as expected, when I tried in a fresh macOS Application template with no modifications other than changing the Principal class and adding the code to define my subclass.


The issue is that the above changes had no effect whatsoever in an existing application project with a different structure.

To reproduce this in the fresh project, I removed the .xib/.storyboard file, and replaced the AppDelegate.swift file with only a main.swift file containing the following:

import Cocoa
class Application: NSApplication, NSApplicationDelegate {
    func applicationShouldTerminateAfterLastWindowClosed(_ _: NSApplication) -> Bool {
        return true
    }
}
NSApplication.shared.delegate = (NSApp as! Application)
NSApp.run()

In this case, the as! downcast will fail, meaning that the application object created by NSApplication.shared is not an instance of my custom class, even though I set the Principal class to the same string that worked before. Removing Info.plist entirely seems to have no effect at all.

I suspect that the other project structure comprising a delegate annotated with @NSApplicationMain somehow registers the value of Principal class in Info.plist to be used to instantiate the application instance, but using NSApplication.shared to manually initialize the application does not include that step.

How can I use a custom class for my application object in this particular case where I use NSApplication.shared directly?

In case this matters my macOS version is 10.15.7 and my Swift version is 5.3.2.


Solution

  • The solution is to access the shared property though the subclass I want to instantiate. This means using Application.shared instead of NSApplication.shared. The code responsible for loading the Principal class is not in the shared method and is not automatically invoked before the entry point of the program.

    print(Application.shared is Application) // Prints true
    NSApp.run()