Search code examples
swiftmacosbundle

Swift: Can I get information for an application which is not running?


I am writing an application which stores data about other applications. I can find the current front most application and its information easily enough:

let frontApp = NSWorkspace.shared.frontmostApplication!

and from there I can get:

let currentAppName = currentApp.localizedName ?? "Dunno"
let currentAppIcon = currentApp.icon

If the application is not in the foreground, I know I can iterate through other running applications:

let apps = NSWorkspace.shared.runningApplications
for app in apps as [NSRunningApplication] {
    print(app.localizedName ?? "whatever")
    print(app.bundleIdentifier ?? "bundle")
}

I would like to get the same information about a application which is not currently running, but for which I do have the bundle identifier. How can I get this?


Solution

  • First, get the application's bundle using

    let bundleURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: someBundleID)!
    let bundle = Bundle(url: bundleURL)!
    

    Once you have the bundle, you can get most of the things that you can get from a NSRunningApplication:

    • bundleIdentifier you already know.
    • bundleURL you just got with the code above.
    • executableArchitecture is now bundle.executableArchitectures, with a plural. Because it's not running, you get all the architectures that it can be run on, rather than the specific architecture that it is running on.
    • executableURL you can get with bundle.executableURL, no problems there.
    • launchDate, isFinishedLaunching, processIdentifier, ownsMenuBar are nonsensical to get, since the application is not running.

    Now we are left with icon and localizedName. The methods I propose for getting these are less reliable. To get icon, do

    NSWorkspace.shared.icon(forFile: bundleURL.path)
    

    I'm sure this produces the same result as NSRunningApplication.icon at least 99% of the time, but I'm not sure if there are edge cases...

    To get localizedName is more unreliable. There are 2 ways, neither of which are good. Choose the one that fits your purpose the best

    • FileManager.default.displayName(atPath: bundleURL.path) This gives you the localised name, but if the user renames the application, it will return the new name, rather than the bundle name

    • Getting CFBundleDisplayName from the bundle's localizedInfoDictionary, falling back on CFBundleName and infoDictionary. This for some reason doesn't give the localised name, even though I said localizedInfoDictionary.

      let infoDict = (bundle.localizedInfoDictionary ?? bundle.infoDictionary)
      let localizedName = infoDict?["CFBundleDisplayName"] ?? infoDict?["CFBundleName"]