2022-10-06
I am knee-deep in macOS systems programming lately, so I solved an interesting problem the other day: gracefully terminating a proper MacOS application on SIGINT signal.
Based on this website, SIGINT is: sent to a process by its controlling terminal when a user wishes to interrupt the process. This is typically initiated by pressing Ctrl-C, but on some systems, the "delete" character or "break" key can be used.
SIGTERM would have been a more natural choice, but it was already taken for something else. Anyways, they are almost identical in meaning.
What I did, is the following:
// appDelegate.swift
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
private var signal: DispatchSourceSignal?
let queue = DispatchQueue(label: "com.company.signalQueue") // 1
func applicationDidFinishLaunching(_ notification: Notification) {
// Catch SIGINT signal and stop subprocess gracefully
self.signal = handleSigint {
NSApp.terminate(self) // 2
}
// Other stuff in applicationDidFinishLaunching...
}
// Gracefully stop subprocess before application termination
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
if subprocessIsRunning() {
stopSubProcess()
return .terminateLater // 3
}
return .terminateNow // 4
}
}
extension AppDelegate {
// Catch and handle SIGINT
func handleSigint(handler: @escaping DispatchSourceProtocol.DispatchSourceHandler) -> DispatchSourceSignal {
Darwin.signal(SIGINT, SIG_IGN)
let signal = DispatchSource.makeSignalSource(signal: SIGINT, queue: self.queue)
signal.setEventHandler(handler: handler)
signal.resume()
return signal
}
}
// subprocess.swift
...
func stopSubProcess() {
DispatchQueue.main.async {
self.stop { result in
defer {
NSApp.reply(toApplicationShouldTerminate: true) // 5
}
if let error = result {
// handle error
} else {
// success
}
}
}
}
...
That's all folks, see you later!