Process() 是 Swift(或者更准确地说是 Foundation 框架)中用来在macOS 或其他 Unix 系统中启动和控制子进程的类。
它允许 Swift 程序中执行系统命令,比如运行终端命令、调用外部工具、执行 shell 脚本等等。
导入Foundation框架使用:
import Foundation
Process可以简单的理解为程序内嵌的“终端”,可以运行二进制工具,执行shell脚本文件等命令。
基本用法
import Foundation
let process = Process()
process.executableURL = URL(fileURLWithPath: "/bin/echo")
process.arguments = ["Hello, World!"]
do {
try process.run()
process.waitUntilExit()
} catch {
print("运行失败:\(error)")
}
这段代码等价于在终端中运行:
/bin/echo "Hello, World!"
常用属性
1、executableURL:URL?类型,要运行的命令路径(必须设置)。
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
在Xcode中运行二进制文件,可以使用Bundle.main.path获取命令路径:
guard let pngquant = Bundle.main.path(forResource: "pngquant", ofType: nil) else { return }
let process = Process()
process.executableURL = URL(fileURLWithPath: pngquant) // 使用 Bundle 中的二进制文件路径
2、arguments:[String]?类型,命令行参数,传给可执行文件的参数。
process.arguments = ["ls", "-la", "/Users"]
3、standardOutput:Any?类型,设置子进程的标准输出,可以设为 Pipe() 获取输出,或设为 nil 使用默认行为(输出到终端)。
let pipe = Pipe()
process.standardOutput = pipe
配合Pipe()使用,获取输出信息。
4、standardInput:Any?类型,子进程的输入通道,也可用 Pipe() 或 FileHandle 传输数据到子进程。
let inputPipe = Pipe()
process.standardInput = inputPipe
5、standardError:Any?类型,错误输出流,通常设置为 Pipe() 来捕获错误信息。
let errorPipe = Pipe()
process.standardError = errorPipe
6、environment:[String: String]?类型,用于设置子进程的环境变量。默认继承当前进程的环境变量。
process.environment = ["PATH": "/usr/local/bin"]
7、currentDirectoryURL:URL?类型,子进程启动时的工作目录。默认是当前进程的工作目录。
process.currentDirectoryURL = URL(fileURLWithPath: "/Users/fangjunyu/Desktop")
8、terminationStatus:Int32类型,子进程退出后的状态码,通常 0 表示成功。
注意:只能在 waitUntilExit() 之后访问。
process.waitUntilExit()
print("状态码: \(process.terminationStatus)")
9、isRunning:检查子进程是否正在运行。
if process.isRunning {
print("进程仍在运行")
}
10、terminationHandler:当子进程结束后触发的回调,用于异步处理。
process.terminationHandler = { proc in
print("进程已结束,状态码:\(proc.terminationStatus)")
}
使用示例
1、Swift调用pngquant命令行工具
let process = Process()
process.executableURL = URL(fileURLWithPath: pngquant)
process.arguments = ["--quality=\(quality)", "--output", outputURL.path, inputURL.path]
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
do {
try process.run() // 启动
process.waitUntilExit() // 等待直到退出
let logData = pipe.fileHandleForReading.readDataToEndOfFile() // 获取压缩数据的日志
if let log = String(data: logData, encoding: .utf8) {
print("pngquant 日志:\n\(log)")
}
if process.terminationStatus == 0 {
print("压缩完成")
return
} else {
print("压缩失败,退出码:\(process.terminationStatus)")
return
}
} catch {
print("运行 pngquant 失败:\(error)")
}
1、创建子进程对象
let process = Process()
创建一个新的子进程对象,用来执行系统命令或外部工具。
2、设置外部命令路径
process.executableURL = URL(fileURLWithPath: pngquant)
设置要执行的外部命令的路径,例如 “/usr/local/bin/pngquant”。
这里的pngquant实际上是从Bundle传入的:
guard let pngquant = Bundle.main.path(forResource: "pngquant", ofType: nil) else { return }
3、传递命令行参数
process.arguments = ["--quality=\(quality)", "--output", outputURL.path, inputURL.path]
设置传递给 pngquant 的命令行参数。相当于执行终端命令:
pngquant --quality=65-80 --output /输出路径.png /输入路径.png
4、设置子进程标准输出和错误输出
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
设置子进程的标准输出和错误输出,都导向这个 pipe。这样就可以读取 pngquant 的输出日志(包括正常输出和错误信息)。
5、启动子进程
try process.run()
如果路径不合法、权限不足等,run() 会抛出异常,所以用了 try。
6、等待子进程
process.waitUntilExit()
等待子进程运行完成。这是一个阻塞操作,直到 pngquant 压缩结束。
7、读取输出内容
let logData = pipe.fileHandleForReading.readDataToEndOfFile()
从管道中读取完整输出内容。这个输出可能是压缩的进度、警告或错误信息。
8、输出日志
if let log = String(data: logData, encoding: .utf8) {
print("pngquant 日志:\n\(log)")
}
将日志转换为字符串,便于开发调试或记录。
9、子进程退出码
if process.terminationStatus == 0 {
// 成功
completion(true)
} else {
// 失败
completion(false)
}
terminationStatus 是子进程退出码:
0 表示正常完成(压缩成功)。
非 0 表示有错误(如文件不存在、压缩失败)。
常见用途
1、运行系统命令(如 zip、cp、open)。
2、调用图像压缩工具(如 pngquant)。
3、启动后台任务。
4、自动化脚本调用。
总结
使用Process()命令,可以在Swift中执行终端的命令,在实际应用中,可以运行二进制工具,从而实现更多的功能。
相关文章
1、Swift压缩PNG图像:https://fangjunyu.com/2025/07/14/swift%e5%8e%8b%e7%bc%a9png%e5%9b%be%e5%83%8f/
扩展知识
1、waitUntilExit和terminationHandler的区别
在使用示例中,我们使用waitUntilExit()等待子进程结束,但是waitUntilExit()是同步(阻塞)调用。
try process.run()
process.waitUntilExit()
// 直到进程执行完,这行代码才会继续
print("Process done with code: \(process.terminationStatus)")
因此,可以使用terminationHandler替代waitUntilExit(),terminationHandler是异步回调,在进程结束后回调,在主线程中使用更安全,不会阻塞线程。
let pipe = Pipe()
process.standardOutput = pipe
process.terminationHandler = { process in
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8) ?? ""
print("异步日志:\(output)")
print("异步退出码:\(process.terminationStatus)")
}
try process.run()
// 立即返回,进程结束时才会调用 terminationHandler
2、terminationStatus的作用
无论使用waitUntilExit() 还是 terminationHandler,都应该使用 process.terminationStatus 来判断进程的退出状态码,因为:
terminationStatus 是系统在子进程退出后设置的属性,告知“这次执行是成功还是失败(或其他状态)”。
1、用 waitUntilExit() 时:
try process.run()
process.waitUntilExit()
if process.terminationStatus == 0 {
print("执行成功")
} else {
print("执行失败,状态码:\(process.terminationStatus)")
}
因为 waitUntilExit 会确保进程结束,然后才能安全访问 terminationStatus。
2、用 terminationHandler 时:
try process.run()
process.terminationHandler = { proc in
if proc.terminationStatus == 0 {
print("执行成功")
} else {
print("执行失败,状态码:\(proc.terminationStatus)")
}
}
因为 terminationHandler 只会在进程结束时调用,可以放心使用 terminationStatus。
注意事项:不能在进程还没结束前就访问 terminationStatus,否则:
try process.run()
// 错误做法!这时候 status 还没有正确值
print(process.terminationStatus) // 可能是未定义状态