Headless @main AppKit app?

Is it possible to create a headless macOS app using @main on an app delegate class without code to explicitly instantiate it? No storyboard, no SwiftUI, just:

@main
class AppDelegate: NSObject, NSApplicationDelegate {

	func applicationDidFinishLaunching(_ notification: Notification) {
        //  Never gets called.
    }

I understand this to be the case, but I created my Xcode project from a SwiftUI template. I've removed the SwfitUI app definition, and there are no Info.plist keys referring to anything. I tried adding NSPrincipleClass = NSApplication, but this key doesn't appear in the built Info.plist, and it also seems to be a build setting in the target.

I also tried @NSApplicationMain but it did’t seem to change anything.

After creating a macOS app with XIB interface and deleting the MainMenu.XIB and Assets.xcassets files, this seems to work.

//
//  AppDelegate.swift
//  HeadlessMacOSApp
//

@main
enum HeadlessApp {
    static func main () {
        print (type (of:self), #function)
    }
}
Build/Products/Debug/HeadlessMacOSApp.app/Contents/MacOS
> ./HeadlessMacOSApp
--> HeadlessApp.Type main()

PS: I use this setup to start processes running XIP services from the command line.

I don't know if this is the issue, but the correct spelling is NSPrincipalClass.

1 Like

Typo in my post, not in the code.

1 Like

I'm trying to run an AppKit app, and main should be synthesized. I'm able to make it work by explicitly providing a main that instantiates my app delegate and calls NSApplicationMain, but my understanding is Xcode should do that for me.

Compiler does call NSApplicationMain for you.
If you add symbolic breakpoint for NSApplicationMain, you can see (stepping through assembler) that NSApplicationMain does indeed run and initializes NSApplication instance. It's just that if there is no storyboard to load, it tries to load nib file, which fails because there is none in your case. But since nib file was never named in the first place it decides to just skip to [NSApplication run].
This results in NSApplication running, but your code never gets to run, because delegate object is never initialized.

It seems your options are either to manually write main or keep storyboard/xib in project, just remove any windows from them (be sure you do not remove AppDelegate association). Second option will result in application running without window, but with menu.

1 Like

There’s no such thing as a headless AppKit app—NSApplication needs to connect to the WindowServer in order to consume events.

A windowless AppKit app is simple to create—just delete the window from your MainMenu.xib and never create another one. As @ibex pointed out, if your app delegate instance lives in MainMenu.nib, this is the only way it will be created.

1 Like

Also, don’t forget to set either the LSBackgroundOnly property or the LSUIElement property. Use the first if you have absolutely no UI and the second if you have limited UI [1].

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

[1] For example, something like the Spotlight window you get when you press command-Space.

3 Likes

Is that still needed even when using

		NSApp.setActivationPolicy(.accessory)

Needed? Perhaps not.

Recommended? Absolutely.

Changing your app’s activation policy dynamically can result in all sorts of weirdness. Most apps don’t need to do that, and thus are much better off declaring it statically via one of those Info.plist properties.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

If only the docs would include comments like this. There is so much useful knowledge in your Comments, that is sadly so well hidden in the vastness of the internet :cry:

2 Likes