如何在 macOS 应用终止时终止后台长期运行的进程?
问题:macOS应用终止时如何连带终止启动的子进程
在我的 macOS 应用中,我通过以下方式启动一个长期运行的进程(本地服务器):
let process = Process() process.launchPath = "/bin/zsh" process.arguments = ["-c"] + ["'\(command)'"] try process.run()
我发现当应用终止时,该进程并未随之终止,而是继续运行。如何实现无论应用以何种方式终止(如强制退出、kill -9、崩溃等),都能终止该进程?
解决方案
1. 正常退出/优雅终止场景:监听应用退出事件主动终止子进程
当应用正常退出(比如用户点击退出按钮),可以监听系统的退出通知,在回调里主动杀死子进程:
// 将Process实例保存为类属性,避免被提前释放 var serverProcess: Process? func startServer() throws { let process = Process() process.launchPath = "/bin/zsh" // 注意:无需给command加单引号,Process会自动处理参数转义,避免命令注入风险 process.arguments = ["-c", command] try process.run() serverProcess = process // 监听应用即将终止的通知 NotificationCenter.default.addObserver( self, selector: #selector(terminateServer), name: NSApplication.willTerminateNotification, object: nil ) } @objc private func terminateServer() { serverProcess?.terminate() serverProcess?.waitUntilExit() }
2. 强制退出/kill -9/崩溃场景:用进程组关联父子进程
上面的方法在应用被强制杀死或崩溃时会失效,因为应用没有机会执行退出回调。这种情况可以把子进程加入到应用的进程组中,父进程终止时系统会向整个进程组发送信号:
func startServer() throws { let process = Process() process.launchPath = "/bin/zsh" process.arguments = ["-c", command] // 将子进程归入当前应用的进程组 process.qualityOfService = .userInitiated if let appPid = getpid() { process.setProcessGroup(appPid) } try process.run() serverProcess = process }
原理:当应用进程被终止时,系统会向其所属进程组发送SIGHUP信号;如果是kill -9这种强制终止,系统会直接回收整个进程组的资源,子进程会随之终止。
补充:如果子进程本身忽略SIGHUP信号,可在启动命令前加上trap "exit 0" SIGHUP;,确保子进程响应信号。
3. 极端场景兜底:子进程主动检测父进程存活状态
如果子进程意外脱离进程组,可以让子进程定期检查父进程是否存活,一旦发现父进程不存在就自行退出。修改启动的command,加入检测逻辑:
# 先启动检测父进程的后台任务,再执行原服务器命令 trap "exit 0" SIGHUP; while true; do if ! ps -p $PPID > /dev/null; then exit 0; fi; sleep 1; done & # 此处替换为你的原服务器启动命令
将这段代码整合到Swift的command变量中,子进程会每隔1秒检查父进程(应用)是否存在,不存在则自动退出。
内容的提问来源于stack exchange,提问作者Guig




