Requirements
- iOS 12.0+ / macOS 10.11+ / tvOS 12.0+ / watchOS 2.0+ / visionOS 1.0+
- Or Linux with zlib development package
- Xcode 11.0
- Swift 4.0
CocoaPods
CocoaPods is a dependency manager for Objective-C and Swift.
To learn more about setting up your project for CocoaPods, please refer to the official documentation.
To integrate ZIP Foundation into your Xcode project using CocoaPods, you have to add it to your project’s Podfile
:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target '<Your Target Name>' do
pod 'ZIPFoundation', '~> 0.9'
end
Afterwards, run the following command:
$ pod install
Zipping Files and Directories
To zip a single file you simply pass a file URL representing the item you want to zip and a destination URL to 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)")
}
By default, archives are created without any compression. To create compressed ZIP archives, the optional compressionMethod
parameter has to be set to .deflate
.
The same method also accepts URLs that represent directory items. In that case, zipItem
adds the directory content of sourceURL
to the archive.
By default, a root directory entry named after the lastPathComponent
of the sourceURL
is added to the destination archive. If you don’t want to preserve the parent directory of the source in your archive, you can pass shouldKeepParent: false
.
Unzipping Archives
To unzip existing archives, you can use FileManager.unzipItem(at sourceURL: URL, to destinationURL: URL)
.
This recursively extracts all entries within the archive to the destination 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)")
}
Creating Archives
To create a new Archive
, pass in a non-existing file URL and 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
}
Adding and Removing Entries
You can add or remove entries to/from archives that have been opened with .create
or .update
AccessMode
.
To add an entry from an existing file, you can pass a relative path and a base URL to addEntry
. The relative path identifies the
entry within the ZIP archive. The relative path and the base URL must form an absolute file URL that points to the file you want to add to
the archive:
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)")
}
Alternatively, the addEntry(with path: String, fileURL: URL)
method can be used to add files that are not sharing a common base directory.
The fileURL
parameter must contain an absolute file URL that points to a file, symlink or directory on an arbitrary file system location.
The addEntry
method accepts several optional parameters that allow you to control compression, memory consumption and file attributes.
You can find detailed information about that parameters in the method’s documentation.
To remove an entry, you need a reference to an entry within an archive that you can pass to 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)")
}
Closure based Reading and Writing
ZIP Foundation also allows you to consume ZIP entry contents without writing them to the file system.
The extract
method accepts a closure of type Consumer
. This closure is called during extraction until the contents of an entry are exhausted:
try archive.extract(entry, consumer: { (data) in
print(data.count)
})
The data
passed into the closure contains chunks of the current entry. You can control the chunk size of the entry by providing the optional bufferSize
parameter.
You can also add entries from an in-memory data source. To do this you have to provide a closure of type Provider
to the addEntry
method:
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)
})
The closure is called until enough data has been provided to create an entry of uncompressedSize
. The closure receives position
and size
arguments
so that you can manage the state of your data source.
In-Memory Archives
Besides closure based reading and writing of file based archives, ZIP Foundation also provides capabilities to process in-memory archives. This allows creation or extraction of archives that only reside in RAM. One use case for this functionality is dynamic creation of ZIP archives that are later sent to a client - without performing any disk IO.
To work with in-memory archives the init(data: Data, accessMode: AccessMode)
initializer must be used.
To read or update an in-memory archive, the passed-in data
must contain a representation of a valid ZIP archive.
To create an in-memory archive, the data
parameter can be omitted:
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
Progress Tracking and Cancellation
All Archive
operations take an optional progress
parameter. By passing in an instance of Progress, you indicate that
you want to track the progress of the current ZIP operation. ZIP Foundation automatically configures the totalUnitCount
of the progress
object and continuously updates its completedUnitCount
.
To get notifications about the completed work of the current operation, you can attach a Key-Value Observer to the fractionCompleted
property of your progress
object.
The ZIP Foundation FileManager
extension methods also accept optional progress
parameters. zipItem
and unzipItem
both automatically create a hierarchy of progress objects that reflect the progress of all items contained in a directory or an archive that contains multiple items.
The cancel() method of Progress
can be used to terminate an unfinished ZIP operation. In case of cancelation, the current operation throws an ArchiveError.cancelledOperation
exception.
Credits
ZIP Foundation is written and maintained by Thomas Zoechling.
Twitter: @weichsel.