You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何在 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

火山引擎 最新活动