Is there a main.swift that is equivalent to the @NSApplicationMain annotation?

Creating a new Cocoa project in Xcode gives me the AppDelegate.swift file, which looks like this:

 import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet weak var window: NSWindow! } 

The @NSApplicationMain attribute @NSApplicationMain described here as

NSApplicationMain

Apply this attribute to the class to indicate that it is an application delegate. Using this attribute is equivalent to calling the NSApplicationMain(_:_:) function.

If you do not use this attribute, put the main.swift file with the code at the top level, which calls the NSApplicationMain(_:_:) function as follows:

 import AppKit NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) 

The instructions in the documentation do not work: the AppDelegate class AppDelegate never created. In this answer, vadian offers the following content for main.swift , which works better than the code in the documentation:

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

However, this still does not provide the same behavior as @NSApplicationMain . Consider the above main.swift with the following AppDelegate.swift :

 import Cocoa class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet weak var window: NSWindow! var foo: NSStatusBar! = NSStatusBar.system(); } 

The above AppDelegate.swift works with the @NSApplicationMain annotation, but when using the above main.swift it crashes at runtime with an error

 Assertion failed: (CGAtomicGet(&is_initialized)), function CGSConnectionByID, file Services/Connection/CGSConnection.c, line 127. 

I think this is_initialized error means that @NSApplicationMain sets things up so that AppDelegate is created after some initialization by the NSApplicationMain function. This assumes the following main.swift , which moves delegate initialization after calling NSApplicationMain :

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

However, this also does not work, because NSApplicationMain never returns! The above main.swift equivalent to a broken sentence in the documentation, since the last two lines are dead code.

So I believe that there is some way to pass a reference to my AppDelegate class as an argument to the NSApplicationMain function, so that Cocoa can do its initialization and then instantiate the AppDelegate class. However, I see no way to do this.

Is there a main.swift that provides behavior that is really equivalent to the @NSApplicationMain annotation? If so, what does this main.swift look like? If not, what does @NSApplicationMain and how to change it?

+6
source share
3 answers

The documentation assumes that there is an xib or storyboard that creates an instance of the AppDelegate class through an object (blue cube) in Interface Builder. In this case, both

  • main.swift containing NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

and

  • @NSApplicationMain in class AppDelegate

behave exactly the same.

If there is no xib or storyboard, you must initialize the AppDelegate class, assign it to NSApplication.shared.delegate and run the application. You must also consider the order in which items appear. For example, you cannot initialize objects associated with AppKit before calling NSApplication.shared to start the application.


For example, with this slightly modified syntax

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

you can initialize the status bar in AppDelegate outside of applicationDidFinishLaunching :

 let statusItem = NSStatusBar.system().statusItem(withLength: -1) 

because NSApplication.shared() to launch the application is called before initializing the AppDelegate class.

+6
source

Here is what I did to run the application without the @NSApplicationMain annotation and the NSApplicationMain(_, _) function when using the Storyboard with the initial NSWindowController generated by the Xcode application template (with the minor changes related to the Main Menu described below).

File: AppConfig.swift (Swift 4)

 struct AppConfig { static var applicationClass: NSApplication.Type { guard let principalClassName = Bundle.main.infoDictionary?["NSPrincipalClass"] as? String else { fatalError("Seems like `NSPrincipalClass` is missed in `Info.plist` file.") } guard let principalClass = NSClassFromString(principalClassName) as? NSApplication.Type else { fatalError("Unable to create `NSApplication` class for `\(principalClassName)`") } return principalClass } static var mainStoryboard: NSStoryboard { guard let mainStoryboardName = Bundle.main.infoDictionary?["NSMainStoryboardFile"] as? String else { fatalError("Seems like `NSMainStoryboardFile` is missed in `Info.plist` file.") } let storyboard = NSStoryboard(name: NSStoryboard.Name(mainStoryboardName), bundle: Bundle.main) return storyboard } static var mainMenu: NSNib { guard let nib = NSNib(nibNamed: NSNib.Name("MainMenu"), bundle: Bundle.main) else { fatalError("Resource `MainMenu.xib` is not found in the bundle `\(Bundle.main.bundlePath)`") } return nib } static var mainWindowController: NSWindowController { guard let wc = mainStoryboard.instantiateInitialController() as? NSWindowController else { fatalError("Initial controller is not `NSWindowController` in storyboard `\(mainStoryboard)`") } return wc } } 

File main.swift (Swift 4)

 // Making NSApplication instance from `NSPrincipalClass` defined in `Info.plist` let app = AppConfig.applicationClass.shared // Configuring application as a regular (appearing in Dock and possibly having UI) app.setActivationPolicy(.regular) // Loading application menu from `MainMenu.xib` file. // This will also assign property `NSApplication.mainMenu`. AppConfig.mainMenu.instantiate(withOwner: app, topLevelObjects: nil) // Loading initial window controller from `NSMainStoryboardFile` defined in `Info.plist`. // Initial window accessible via property NSWindowController.window let windowController = AppConfig.mainWindowController windowController.window?.makeKeyAndOrderFront(nil) app.activate(ignoringOtherApps: true) app.run() 

Note regarding the MainMenu.xib file:

The Xcode application template creates a storyboard with an Application Scene that contains the Main Menu . At the moment, there seems to be no programmatically loading the Main Menu from the Application Scene . But there is an Xcode Main Menu file template that creates the MainMenu.xib file, which we can download programmatically.

+3
source

Replace the default Cocoa AppDelegate.swift project with main.swift . The application will behave as before. Thus, the following code provides the semantics of the @NSApplicationMain annotation.

 import Cocoa class AppDelegate: NSObject, NSApplicationDelegate { } let myApp: NSApplication = NSApplication.shared() let myDelegate: AppDelegate = AppDelegate() myApp.delegate = myDelegate let mainBundle: Bundle = Bundle.main let mainNibFileBaseName: String = mainBundle.infoDictionary!["NSMainNibFile"] as! String mainBundle.loadNibNamed(mainNibFileBaseName, owner: myApp, topLevelObjects: nil) _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) 

(I built this with a lot of help from the Vanadian answer . If there are any differences in behavior between the above and the standard Cocoa project application, please let me know.)

0
source

Source: https://habr.com/ru/post/1265642/


All Articles