标签: Swift

  • Swift 通过执行二进制文件增强功能

    Swift 使用 Process 可以执行二进制文件从而实现一定功能,例如下面的完整 图片压缩 例子

    pngquant 原本是没有 Swift 版本的,在我之前开发的的 macOS 原生 SwiftUI 版本切图上传工具中的图片压缩功能,手动实现起来极其复杂而且效果可能很差,所以使用了这种方法,目前稳定使用了数月

    大致步骤:

    1. 读取内置二进制文件 Bundle.main.path(forResource: "pngquant", ofType: nil)
    2. 创建 Process 实例 let process = Process()
    3. 在 Process 中指定二进制文件 process.executableURL = URL(fileURLWithPath: oxipngPath)
    4. 加入参数 process.arguments = ["--quality", "0-70", "--force", "-o", outputFilePath, inputFilePath]
    5. 创建管道 let pipe = Pipe(); process.standardOutput = pipe
    6. 启动进程 try process.run(); process.waitUntilExit()
    7. 非必要 读取输出_ = pipe.fileHandleForReading.readDataToEndOfFile()
    8. 在执行结束后进行下一步操作 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)
        }
    }