Swift 通过执行二进制文件增强功能
在 Swift
使用 Process
可以执行二进制文件从而实现一定功能,例如下面的完整 图片压缩 例子
pngquant
原本是没有 Swift
版本的,在我之前开发的的 macOS
原生 SwiftUI
版本切图上传工具中的图片压缩功能,手动实现起来极其复杂而且效果可能很差,所以使用了这种方法,目前稳定使用了数月
大致步骤:
- 读取内置二进制文件
Bundle.main.path(forResource: "pngquant", ofType: nil)
- 创建 Process 实例
let process = Process()
- 在 Process 中指定二进制文件
process.executableURL = URL(fileURLWithPath: oxipngPath)
- 加入参数
process.arguments = ["--quality", "0-70", "--force", "-o", outputFilePath, inputFilePath]
- 创建管道
let pipe = Pipe(); process.standardOutput = pipe
- 启动进程
try process.run(); process.waitUntilExit()
非必要
读取输出_ = pipe.fileHandleForReading.readDataToEndOfFile()
- 在执行结束后进行下一步操作
if process.terminationStatus == 0 { // 结束 }
import Foundation
// 获取指定路径的文件大小
func fileSize(atPath path: String) -> UInt64? {
do {
// 尝试获取指定路径文件的属性
let attributes = try FileManager.default.attributesOfItem(atPath: path)
// 返回文件大小属性(如果存在),并转换为 UInt64 类型
return attributes[.size] as? UInt64
} catch {
// 如果无法获取文件属性,则打印错误信息
print("无法获取文件大小: \(error)")
return nil
}
}
// 使用 pngquant 压缩文件并计算压缩效果
func compress(url: String, outputPath: String) -> (originalFileSize: UInt64?, compressedFileSize: UInt64?, compressionRate: Double?) {
// 尝试在应用程序包中找到 pngquant 二进制文件的路径
guard let oxipngPath = Bundle.main.path(forResource: "pngquant", ofType: nil) else {
fatalError("无法找到 pngquant 二进制文件")
}
// 使用 fileSize 函数获取原始文件大小
guard let originalFileSize = fileSize(atPath: url) else {
print("无法获取原始文件大小")
return (nil, nil, nil)
}
// 设置 Process 以运行 pngquant 命令
let process = Process()
process.executableURL = URL(fileURLWithPath: oxipngPath)
// 定义 pngquant 命令的参数
process.arguments = ["--quality", "0-70", "--force", "-o", outputPath, url]
do {
// 尝试运行该进程
try process.run()
// 等待进程完成
process.waitUntilExit()
// 检查进程是否成功完成
if process.terminationStatus == 0, let compressedFileSize = fileSize(atPath: outputPath) {
// 计算压缩率(百分比)
let compressionRate = Double(originalFileSize - compressedFileSize) / Double(originalFileSize) * 100
// 返回原始大小、压缩后大小和压缩率
return (originalFileSize, compressedFileSize, compressionRate.rounded())
} else {
// 如果优化失败,打印错误信息
print("PNG 优化失败")
return (originalFileSize, nil, nil)
}
} catch {
// 如果进程启动失败,打印错误信息
print("启动失败: \(error)")
return (originalFileSize, nil, nil)
}
}