Zip
SkipZip is a Skip framework that provides support for creating and extracting zip archives.
To include this framework in your project, add the following
dependency to your Package.swift file:
let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-zip.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipZip", package: "skip-zip") ]) ])Local Zip Archives
Section titled “Local Zip Archives”Use ZipReader and ZipWriter for working with zip files on disk:
import SkipZip
// Reading a zip fileif let reader = ZipReader(path: "/path/to/archive.zip") { try reader.first() while true { if let name = try reader.currentEntryName { print("Entry: \(name)") } if let data = try reader.currentEntryData { // Process entry data... } if !(try reader.next()) { break } } try reader.close()}
// Writing a zip fileif let writer = ZipWriter(path: "/path/to/output.zip", append: false) { try writer.add(path: "hello.txt", data: "Hello, World!".data(using: .utf8)!, compression: 5) try writer.close()}Remote Zip Archives
Section titled “Remote Zip Archives”RemoteZipReader allows reading individual entries from a remote zip archive over HTTP
without downloading the entire file. It uses HTTP Range requests to fetch only the
archive’s central directory (metadata) and the specific entries you request.
This is useful for selectively extracting files from large remote archives where downloading the full archive would be impractical.
Requirements: The HTTP server must support Range requests (indicated by the
Accept-Ranges: bytes header). If the server does not support range requests,
the operation will fail with a descriptive error.
import SkipZip
// Open a remote zip archive (fetches only the metadata)let reader = try await RemoteZipReader.open(url: URL(string: "https://example.com/archive.zip")!)
// Browse entries without downloading their datafor entry in reader.entries { print("\(entry.name): \(entry.uncompressedSize) bytes")}
// Fetch and decompress a specific entrylet data = try await reader.data(forEntryNamed: "README.md")print(String(data: data, encoding: .utf8)!)Configuration
Section titled “Configuration”RemoteZipConfiguration controls retry behavior for transient network failures.
Retryable conditions include network errors and HTTP status codes 429, 502, 503, and 504.
let config = RemoteZipConfiguration( maxRetries: 5, // Retry up to 5 times (default: 3) initialBackoffInterval: 1.0, // Wait 1 second before first retry (default: 0.5) backoffMultiplier: 2.0, // Double the wait time after each retry (default: 2.0) maxBackoffInterval: 60.0, // Cap backoff at 60 seconds (default: 30.0) endOfFileReadSize: 131_072 // Read last 128KB to find EOCD (default: 65536))
let reader = try await RemoteZipReader.open( url: archiveURL, session: myURLSession, // Optional: provide a custom URLSession configuration: config)Custom URLSession
Section titled “Custom URLSession”You can pass a custom URLSession for authentication, caching, or proxy configuration:
let sessionConfig = URLSessionConfiguration.defaultsessionConfig.httpAdditionalHeaders = ["Authorization": "Bearer token123"]let session = URLSession(configuration: sessionConfig)
let reader = try await RemoteZipReader.open(url: archiveURL, session: session)Error Handling
Section titled “Error Handling”All errors are reported as RemoteZipError with descriptive messages:
do { let reader = try await RemoteZipReader.open(url: archiveURL) let data = try await reader.data(forEntryNamed: "file.txt")} catch let error as RemoteZipError { switch error { case .serverDoesNotSupportRangeRequests: print("Server doesn't support Range requests") case .entryNotFound(let name): print("Entry '\(name)' not found") case .retryLimitExceeded(let count, let lastError): print("Failed after \(count) retries: \(lastError)") default: print(error.localizedDescription) }}Supported Compression Methods
Section titled “Supported Compression Methods”RemoteZipReader supports the two most common zip compression methods:
- STORE (method 0): No compression; data is returned as-is.
- DEFLATE (method 8): Standard zip compression; data is decompressed automatically.
ZIP64 extensions are supported for archives and entries larger than 4 GB.
Building
Section titled “Building”This project is a Swift Package Manager module that uses the Skip plugin to build the package for both iOS and Android.
Testing
Section titled “Testing”The module can be tested using the standard swift test command
or by running the test target for the macOS destination in Xcode,
which will run the Swift tests as well as the transpiled
Kotlin JUnit tests in the Robolectric Android simulation environment.
Parity testing can be performed with skip test,
which will output a table of the test results for both platforms.