LaunchAtLoginの実装
追記(2024-03-16)
- より簡単に実装ができるようになったので、OSが対応しているなら下記がおすすめです!
概要
- アプリでよくある
Launch at Login
の実装を行う。 -
LaunchAtLogin
の仕組みとしてはざっくり以下の通り。- 起動したいアプリ(以下メインアプリ)にヘルパーアプリを内包させる。
- ヘルパーアプリをユーザログイン時に起動するよう登録。
- (ユーザログイン時)ヘルパーアプリからメインアプリを起動させ、ヘルパーアプリを終了させる。

GitHub
参考
-
How to launch a macOS app at login?
- 主に参考にした。ただ記事のが古いのかいくつか手直しが必要。
- 例えばlaunchApplication(_:)はDeprecatedなので
NSWorkspace.shared.urlForApplication()
で置き換える。
- 上記の記事である
@NSApplicationMain
を記述せずに、main.swift
を作るとうまくいく。 -
Swift: macOSでLaunch at loginを実装する
- 同じ方向性。アプリの起動のコードを参考にしました。
- https://github.com/Kyome22/ShiftWindow
NSWorkspace.shared.urlForApplication()
で本体アプリのファイルパスを取得する。
- ライブラリもあるけど現在はCarthageでしか使えない?SPMで入れてみるもうまく動かず。
-
1024jp/Preferences-Demo
- UserDefaultsを使ったCocoaBindingを参考にしました。
プロジェクト設定
メインアプリ
- メインのアプリの作成
- 以下からヘルパーアプリを作成
- メインアプリにヘルパーアプリを内包させる設定
-
ServiceManagement.framework
を追加
ヘルパーアプリ
-
Build Settings
のSkip Install
をYES
-
Info.plist
にApplication is background only
を追加しValueをYES
にする
- ヘルパーアプリ側のUIがいらないので下記を削除
-
Main storyboard file base name
のMain
を空にする
実装
メインアプリ
LauncherConst.swift
- ヘルパーアプリのIdentifierを複数箇所で使用するので下記の通り宣言しておく。
import Foundation
struct LauncherConst {
static let launcherAppId = "com.gmail.ikeh1024.MainApplicationLauncher"
}
AppDelegate.swift
- 起動時にヘルパーアプリが起動している場合に、ヘルパーアプリを終了させるようにする。
- DistributedNotificationCenterで異なるアプリ間でやりとりできる。
import Cocoa
import ServiceManagement
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// ヘルパーが起動していたら終了させる
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == LauncherConst.launcherAppId }.isEmpty
if isRunning {
DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
}
}
...
}
ViewContorller.swift
- 下記コードはチェックボックスを選択したときのアクション。
- SMLoginItemSetEnabled(_:_:)でヘルパーアプリのIdentifierを登録することで、ログイン時にヘルパーアプリが起動される。
@IBAction func launchAtLoginCheckboxClicked(_ sender: NSButton) {
SMLoginItemSetEnabled(LauncherConst.launcherAppId as CFString, GeneralPreferences.shared.launchAtLogin.isOn)
}
-
Launch at Login
のチェックボックスは、CocoaBindingを使い、UserDefaultsのlaunchAtLogin
をキーとして同期させている。
ヘルパーアプリ
main.swift
- エントリーポイントとしての実装
import AppKit
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
app.run()
AppDelegate.swift
- メインアプリの起動に関する設定を諸々行う
import Cocoa
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
class AppDelegate: NSObject {
@objc func terminate() {
NSApp.terminate(nil)
}
}
extension AppDelegate: NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// メインアプリの起動確認
let mainAppIdentifier = "com.gmail.ikeh1024.MainApplication"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == mainAppIdentifier }.isEmpty
if !isRunning {
// メインアプリ起動時に終了させる通知を受け取るための設定
DistributedNotificationCenter
.default()
.addObserver(self,
selector: #selector(self.terminate),
name: .killLauncher,
object: mainAppIdentifier)
// メインアプリを起動する
if !isRunning,
let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: mainAppIdentifier) {
let config = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.openApplication(at: url, configuration: config) { _, _ in
}
}
}
else {
// メインアプリが起動していれば何もせずに終了
self.terminate()
}
}
}
ヘルパーアプリが登録されているかの確認方法
-
SMCopyAllJobDictionaries(_:)でヘルパーアプリがログイン時に起動するよう登録されているか確認できる。
- Login Item - cocoaを参考にした。
@IBAction func checkSMLoginButtonClicked(_ sender: Any) {
let jobDicts = SMCopyAllJobDictionaries(kSMDomainUserLaunchd).takeRetainedValue() as NSArray as! [[String:AnyObject]]
let jobEnabled = jobDicts.filter { $0["Label"] as! String == LauncherConst.launcherAppId }.isEmpty == false
print("SMLogin: \(jobEnabled)")
}
- SMCopyAllJobDictionaries(_:)はDeprecatedだが、代替ができるまではこれしか無い?みたい。
SMLoginItemSetEnabled(...) GET counterpart
On SMCopyAllJobDictionaries inside For the specific use of testing the state of a login item that may have been enabled with SMLoginItemSetEnabled() in order to show that state to the user, this function remains the recommended API. A replacement API for this specific use will be provided before this function is removed. We can safely use the method until further notice. Just be sure to check the new SDK releases/updates for the new API and this method's obsolescence. –
Nogurenn
Feb 7 '19 at 8:15
- 実際にXcode上で覗いてみると
libxpc
で置き換わる予定と記載があった。
@available(macOS, introduced: 10.6, deprecated: 10.10)
public func SMCopyAllJobDictionaries(_ domain: CFString!) -> Unmanaged<CFArray>!
/**
(省略)
* @discussion
* This routine is deprecated and will be removed in a future release. A
* replacement will be provided by libxpc.
*/