Swift Package Manager compatible Carthage compatible CocoaPods Compatible Platform Twitter

ZIP Foundation 是一个用于创建、读取和修改 ZIP 压缩文件的库。它使用 Swift 编写,并基于 Apple 的 libcompression,以实现高性能和高能源效率。要了解有关该框架的性能特征的更多信息,您可以阅读这篇博客文章

特性

要求

安装

Swift Package Manager

Swift Package Manager 是一个与 Swift 构建系统集成的依赖项管理器。要了解如何为您的项目使用 Swift Package Manager,请阅读官方文档
要将 ZIP Foundation 添加为依赖项,您必须将其添加到您的 Package.swift 文件的 dependencies 中,并在您的 target 中引用该依赖项。

// swift-tools-version:5.0
import PackageDescription
let package = Package(
    name: "<Your Product Name>",
    dependencies: [
		.package(url: "https://github.com/weichsel/ZIPFoundation.git", .upToNextMajor(from: "0.9.0"))
    ],
    targets: [
        .target(
		name: "<Your Target Name>",
		dependencies: ["ZIPFoundation"]),
    ]
)

添加依赖项后,您可以使用以下命令获取库:

$ swift package resolve

Carthage

Carthage 是一个去中心化的依赖项管理器。
安装说明可以在项目的 README 文件中找到。

要使用 Carthage 将 ZIPFoundation 集成到您的 Xcode 项目中,您必须将其添加到您的 Cartfile

github "weichsel/ZIPFoundation" ~> 0.9

将 ZIPFoundation 添加到 Cartfile 后,您必须通过运行以下命令获取源代码:

carthage update --no-build

获取的项目必须通过将 ZIPFoundation.xcodeproj 拖到 Xcode 的项目导航器中来集成到您的工作区。(请参阅 官方 Carhage 文档。)

CocoaPods

CocoaPods 是一个用于 Objective-C 和 Swift 的依赖项管理器。
要了解有关为 CocoaPods 设置项目的更多信息,请参阅官方文档
要使用 CocoaPods 将 ZIP Foundation 集成到您的 Xcode 项目中,您必须将其添加到您的项目的 Podfile

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target '<Your Target Name>' do
    pod 'ZIPFoundation', '~> 0.9'
end

之后,运行以下命令

$ pod install

用法

ZIP Foundation 提供了两种高级方法来压缩和解压缩项目。两者都作为 FileManager 的扩展实现。
这些方法的功能仿照 macOS 中“归档实用工具”的行为。

压缩文件和目录

要压缩单个文件,您只需传递一个文件 URL,表示您要压缩的项目,以及一个目标 URL 给 FileManager.zipItem(at sourceURL: URL, to destinationURL: URL)

let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var sourceURL = URL(fileURLWithPath: currentWorkingPath)
sourceURL.appendPathComponent("file.txt")
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("archive.zip")
do {
    try fileManager.zipItem(at: sourceURL, to: destinationURL)
} catch {
    print("Creation of ZIP archive failed with error:\(error)")
}

默认情况下,创建的压缩文件不进行任何压缩。要创建压缩的 ZIP 压缩文件,必须将可选的 compressionMethod 参数设置为 .deflate
相同的方法也接受表示目录项目的 URL。在这种情况下,zipItem 会将 sourceURL 的目录内容添加到压缩文件中。
默认情况下,会向目标压缩文件添加一个以 sourceURLlastPathComponent 命名的根目录条目。如果您不想在压缩文件中保留源文件的父目录,则可以传递 shouldKeepParent: false

解压缩文件

要解压缩现有的压缩文件,您可以使用 FileManager.unzipItem(at sourceURL: URL, to destinationURL: URL)
这将递归地将压缩文件中的所有条目提取到目标 URL

let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var sourceURL = URL(fileURLWithPath: currentWorkingPath)
sourceURL.appendPathComponent("archive.zip")
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("directory")
do {
    try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
    try fileManager.unzipItem(at: sourceURL, to: destinationURL)
} catch {
    print("Extraction of ZIP archive failed with error:\(error)")
}

高级用法

ZIP Foundation 还允许您单独访问特定条目,而无需提取整个压缩文件。此外,它还具有增量更新压缩文件内容的能力。

访问单个条目

要访问特定的 ZIP 条目,您必须使用表示现有压缩文件的文件 URL 初始化一个 Archive 对象。完成此操作后,可以通过它们的相对路径检索条目。Archive 符合 Sequence 协议,因此支持下标访问

let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("archive.zip")
let archive = try Archive(url: archiveURL, accessMode: .read)
guard let entry = archive["file.txt"] else {
    return
}
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("out.txt")
do {
    try archive.extract(entry, to: destinationURL)
} catch {
    print("Extracting entry from archive failed with error:\(error)")
}

extract 方法接受可选参数,允许您控制压缩和内存消耗。
您可以在该方法的文档中找到有关这些参数的详细信息。

创建压缩文件

要创建新的 Archive,请传入一个不存在的文件 URL 和 AccessMode.create

let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("newArchive.zip")
let archive = try Archive(url: archiveURL, accessMode: .create)

添加和删除条目

您可以向使用 .create.update AccessMode 打开的压缩文件添加或删除条目。要从现有文件添加条目,您可以将相对路径和基本 URL 传递给 addEntry。相对路径标识 ZIP 压缩文件中的条目。相对路径和基本 URL 必须构成指向您要添加到压缩文件的文件的绝对文件 URL

let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("archive.zip")
let archive = try Archive(url: archiveURL, accessMode: .update)
var fileURL = URL(fileURLWithPath: currentWorkingPath)
fileURL.appendPathComponent("file.txt")
do {
    try archive.addEntry(with: fileURL.lastPathComponent, relativeTo: fileURL.deletingLastPathComponent())
} catch {
    print("Adding entry to ZIP archive failed with error:\(error)")
}

或者,可以使用 addEntry(with path: String, fileURL: URL) 方法来添加共享公共基本目录的文件。fileURL 参数必须包含一个绝对文件 URL,该 URL 指向任意文件系统位置上的文件、符号链接或目录。

addEntry 方法接受多个可选参数,允许您控制压缩、内存消耗和文件属性。
您可以在该方法的文档中找到有关这些参数的详细信息。

要删除条目,您需要引用压缩文件中的条目,您可以将其传递给 removeEntry

guard let entry = archive["file.txt"] else {
    return
}
do {
    try archive.remove(entry)
} catch {
    print("Removing entry from ZIP archive failed with error:\(error)")
}

基于闭包的读取和写入

ZIP Foundation 还允许您使用 ZIP 条目内容,而无需将其写入文件系统。extract 方法接受类型为 Consumer 的闭包。在提取条目的内容耗尽之前,会调用此闭包

try archive.extract(entry, consumer: { (data) in
    print(data.count)
})

传递到闭包的 data 包含当前条目的数据块。您可以通过提供可选的 bufferSize 参数来控制条目的数据块大小。

您还可以从内存数据源添加条目。为此,您必须将类型为 Provider 的闭包提供给 addEntry 方法

let string = "abcdefghijkl"
guard let data = string.data(using: .utf8) else { return }
try? archive.addEntry(with: "fromMemory.txt", type: .file, uncompressedSize: Int64(data.count), bufferSize: 4, provider: { (position, size) -> Data in
    // This will be called until `data` is exhausted (3x in this case).
    return data.subdata(in: Data.Index(position)..<Int(position)+size)
})

调用闭包,直到提供足够的数据以创建 uncompressedSize 的条目。闭包接收 positionsize 参数,以便您可以管理数据源的状态。

内存压缩文件

除了基于闭包的基于文件的压缩文件读取和写入之外,ZIP Foundation 还提供了处理内存压缩文件的功能。这允许创建或提取仅驻留在 RAM 中的压缩文件。此功能的一个用例是动态创建稍后发送给客户端的 ZIP 压缩文件 - 而无需执行任何磁盘 IO。

要使用内存压缩文件,必须使用 init(data: Data, accessMode: AccessMode) 初始化器。
读取更新内存压缩文件,传入的 data 必须包含有效 ZIP 压缩文件的表示形式。
创建内存压缩文件,可以省略 data 参数

let string = "Some string!"
let archive = try Archive(accessMode: .create)
guard let data = string.data(using: .utf8) else { return }
try archive.addEntry(with: "inMemory.txt", type: .file, uncompressedSize: Int64(data.count), bufferSize: 4, provider: { (position, size) -> Data in
    return data.subdata(in: Data.Index(position)..<Int(position)+size)
})
let archiveData = archive.data

进度跟踪和取消

所有 Archive 操作都接受可选的 progress 参数。通过传入 Progress 的实例,您表明您要跟踪当前 ZIP 操作的进度。ZIP Foundation 会自动配置 progress 对象的 totalUnitCount,并持续更新其 completedUnitCount
要获取有关当前操作完成工作的通知,您可以将键值观察器附加到 progress 对象的 fractionCompleted 属性。
ZIP Foundation FileManager 扩展方法也接受可选的 progress 参数。zipItemunzipItem 都自动创建进度对象层次结构,以反映目录或包含多个项目的压缩文件中包含的所有项目的进度。

cancel() 方法的 Progress 可用于终止未完成的 ZIP 操作。如果取消,则当前操作会抛出 ArchiveError.cancelledOperation 异常。

致谢

ZIP Foundation 由 Thomas Zoechling 编写和维护。
Twitter: @weichsel

许可证

ZIP Foundation 在 MIT 许可证下发布。
有关详细信息,请参阅 LICENSE