ZIP Foundation 是一个用于创建、读取和修改 ZIP 归档文件的库。
它使用 Swift 编写并基于 Apple 的 libcompression 以实现高性能和能效。
要了解更多关于框架的性能特性,您可以阅读这篇博客文章。
特性
- 现代 Swift API
- 高性能压缩和解压缩
- 大文件支持
- 内存归档
- 确定性内存消耗
- 与 Linux 兼容
- 无第三方依赖(在 Apple 平台上,Linux 上的 zlib)
- 全面的单元和性能测试覆盖率
- 完整的文档
要求
- iOS 12.0+ / macOS 10.11+ / tvOS 12.0+ / watchOS 2.0+
- 或带有 zlib 开发包的 Linux
- Xcode 11.0
- Swift 4.0
安装
Swift包管理器
Swift包管理器是集成在Swift构建系统中的依赖管理器。若要了解如何将Swift包管理器用于您的项目,请参阅官方文档。
要将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的项目导航器中来集成到您的工作空间中。(参见官方Carthage文档。)
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
的目录内容添加到存档中。
默认情况下, destination archive会在名为sourceURL
的lastPathComponent
的根目录条目添加到目标存档中。如果您不想在存档中保留源父目录,则可以传递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基础库还允许您无需提取整个存档即可访问特定条目。此外,它还提供了按需更新存档内容的能力。
访问单个条目
要获取对特定ZIP条目的访问权限,您必须使用表示现有存档的文件URL初始化一个Archive
对象。完成此操作后,可以通过它们的相对路径检索条目。Archive
符合Sequence
,因此支持索引。
let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("archive.zip")
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
return
}
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
方法接受一些可选参数,这些参数允许您控制压缩率和内存消耗。
您可以在该方法的文档中找到有关这些参数的详细信息。
创建存档
要创建一个新的 存档
,传入一个不存在的文件URL和 AccessMode.create
。
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("newArchive.zip")
guard let archive = Archive(url: archiveURL, accessMode: .create) else {
return
}
添加和移除条目
您可以为使用 .create
或 .update
AccessMode
打开的所有存档添加或删除条目。要从现有文件添加条目,可以将相对路径和基本URL传递给 addEntry
。相对路径确定ZIP存档内的条目标记。相对路径和基本URL必须形成一个指向您想要添加到存档的文件的绝对文件URL
let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("archive.zip")
guard let archive = Archive(url: archiveURL, accessMode: .update) else {
return
}
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
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
参数来控制条目的数据块大小。
您还可以从内存数据源添加条目。为此,您必须向 addEntry
方法提供一个类型为 Provider
的闭包。
let string = "abcdefghijkl"
guard let data = string.data(using: .utf8) else { return }
try? archive.addEntry(with: "fromMemory.txt", type: .file, uncompressedSize: UInt64(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: position..<position+size)
})
此闭包在被提供足够的数据以创建大小为 uncompressedSize
的条目之前被调用。闭包接收 position
和 size
参数,这样您就可以管理数据源的状态。
内存归档
除了基于关闭的文件归档的读取和写入之外,ZIP基金会还提供了处理内存归档的能力。这允许创建或提取仅存在于RAM中的归档。此功能的用例之一是动态创建稍后发送给客户端的ZIP文件归档,而无需执行任何磁盘I/O。
要处理内存归档,必须使用 init(data: Data, accessMode: AccessMode)
初始化器。
要读取或更新内存归档,传入的 data
必须包含有效ZIP归档的表示。
要创建内存归档,可以省略 data
参数
let string = "Some string!"
guard let archive = Archive(accessMode: .create),
let data = string.data(using: .utf8) else { return }
try? archive.addEntry(with: "inMemory.txt", type: .file, uncompressedSize: UInt64(data.count), bufferSize: 4, provider: { (position, size) -> Data in
return data.subdata(in: position..<position+size)
})
let archiveData = archive.data
进度跟踪和取消
所有 Archive
操作都接受一个可选的 progress
参数。通过传递一个 Progress 实例,您表示希望跟踪当前ZIP操作的状态。ZIP基金会自动配置progress
对象的totalUnitCount
,并不断更新其completedUnitCount
。
要获取有关当前操作完成工作的通知,可以为您的progress
对象的fractionCompleted
属性附加一个Key-Value Observer。
ZIP基金会的FileManager
扩展方法也接受可选的progress
参数。《code>zipItem和《code>unzipItem都自动创建一个代表了目录中或包含多个项目的归档的所有项目的进度对象层次结构。
Progress
的cancel()
方法可用于终止未完成的ZIP操作。在取消的情况下,当前操作会抛出ArchiveError.cancelledOperation
异常。
致谢
ZIP基金会由Thomas Zoechling编写和维护。
推特:@weichsel。
许可证
ZIP基金会采用MIT许可证发布。
有关详细信息,请参阅LICENSE。