是否有一个 main.swift 相当于 @NSApplicationMain 注释?

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

在 XCode 中创建一个新的 Cocoa 项目给我一个 AppDelegate.swift 文件,如下所示:

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

The @NSApplicationMain attribute is documented here

NSApplicationMain

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

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

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

文档中的说明不起作用:AppDelegate class 从未实例化。在 , vadian 中建议 main.swift 的以下内容比文档中的代码更好

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

但是,这仍然没有提供与 @NSApplicationMain 相同的行为。考虑将上述 main.swift 与以下 AppDelegate.swift:

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

上面的 AppDelegate.swift 使用 @NSApplicationMain 注释,但是当使用上面的 main.swift 时,它在运行时失败并出现错误

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

我认为这个 is_initialized 错误意味着 @NSApplicationMain 设置了一些东西,以便 AppDelegate 在 一些初始化之后实例化 =19=]函数。这表明以下 main.swift,它将委托初始化移动到 NSApplicationMain 调用之后:

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

但是,这 也行不通 ,因为 NSApplicationMain 永远不会 returns!上面的 main.swift 相当于文档中的 broken suggestion,因为后两行是死代码。

因此我认为必须有一些方法可以将对我的 AppDelegate class 的引用作为参数传递给 NSApplicationMain 函数,这样 Cocoa 就可以做到它的初始化,然后实例化我的 AppDelegate class 本身。但是,我看不出有什么办法。

是否有 main.swift 提供真正等同于 @NSApplicationMain 注释的行为?如果是这样,那 main.swift 是什么样子的?如果不是,@NSApplicationMain 实际上在做什么,我该如何修改它?

文档假设有一个 xib 或故事板通过 Interface Builder 中的对象(蓝色立方体)实例化 AppDelegate class。在这种情况下

  • main.swift包含NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)

  • @NSApplicationMainAppDelegateclass

行为完全相同。

如果没有 xib 或故事板,您负责初始化 AppDelegate class,将其分配给 NSApplication.shared.delegate 和 运行 应用程序。您还必须考虑对象的出现顺序。例如,您无法在调用 NSApplication.shared 启动应用程序之前初始化与 AppKit 相关的对象。


例如,语法略有变化

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

你可以在applicationDidFinishLaunching之外的AppDelegate初始化状态栏:

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

因为 NSApplication.shared() 调用 初始化 AppDelegate class.

之前启动应用程序

将默认 Cocoa 项目的 AppDelegate.swift 替换为以下 main.swift。该应用程序的行为将与以前相同。因此,以下代码提供了 @NSApplicationMain 注释的语义。

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)

(我在 的帮助下构建了这个。如果上面的行为与默认的 Cocoa 项目应用程序之间存在任何差异,请告诉我。)

这是我为了 运行 没有 @NSApplicationMain 注释和函数 NSApplicationMain(_, _) 的应用程序所做的,同时使用由 Xcode 生成的初始 NSWindowController 的 Storyboard应用程序模板(与下面描述的 Main Menu 相关的稍作修改)。

文件: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
   }
}

文件 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()

关于 MainMenu.xib 文件的注释:

Xcode 应用程序模板使用 Application Scene 创建故事板,其中包含 Main Menu。目前似乎没有办法以编程方式从 Application Scene 加载 Main Menu。但是有 Xcode 文件模板 Main Menu,它创建 MainMenu.xib 文件,我们可以通过编程方式加载它。