Работа с ZIP/архивами в Swift (iOS/macOS)
В современной разработке под iOS и macOS часто возникает необходимость сжимать файлы в ZIP-архивы или извлекать данные из них. Это может понадобиться для экономии места, передачи данных по сети или организации бэкапов. В этой статье мы рассмотрим два основных подхода: использование встроенных возможностей Foundation и популярной сторонней библиотеки ZipFoundation.
Встроенные средства Foundation (NSFileCoordinator + низкоуровневая работа)
Apple не предоставляет высокоуровневого API для работы с ZIP в Foundation. Однако, начиная с iOS 12 / macOS 10.14, появилась возможность использовать NSFileCoordinator и NSFileWrapper для некоторых операций. Но для полноценной работы с ZIP чаще всего используют системную утилиту libz через C-интерфейс.
Пример: сжатие данных с помощью libz
import Compression
import Foundation
func compressData(_ data: Data) -> Data? {
let sourceData = data as NSData
let compressedData = NSMutableData(length: data.count)!
var compressedSize = data.count
let result = compression_encode(
compressedData.mutableBytes.assumingMemoryBound(to: UInt8.self),
&compressedSize,
sourceData.bytes.assumingMemoryBound(to: UInt8.self),
data.count,
nil,
COMPRESSION_ZLIB
)
guard result == COMPRESSION_STATUS_OK else { return nil }
compressedData.length = compressedSize
return compressedData as Data
}
Библиотека ZipFoundation (рекомендуемый способ)
ZipFoundation — это легковесная библиотека с открытым исходным кодом для работы с ZIP-архивами на Swift. Она поддерживает iOS, macOS, tvOS и watchOS. Установка через Swift Package Manager:
// В Package.swift или через Xcode
dependencies: [
.package(url: "https://github.com/weichsel/ZIPFoundation.git", from: "0.9.0")
]
Основные возможности ZipFoundation:
- Создание ZIP-архивов из файлов и папок
- Извлечение отдельных файлов или всего архива
- Поддержка паролей (AES-256)
- Работа с потоками для больших файлов
- Совместимость с macOS и iOS
Пример 1: Создание ZIP-архива
import ZIPFoundation
import Foundation
func createZip(from sourceURL: URL, to destinationURL: URL) throws {
let fileManager = FileManager()
// Создаём архив из папки
try fileManager.zipItem(at: sourceURL, to: destinationURL)
// Или из одного файла
// try fileManager.zipItem(at: sourceURL, to: destinationURL,
// shouldKeepParent: false)
}
// Использование
let documents = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first!
let sourceFolder = documents.appendingPathComponent("MyData")
let archiveURL = documents.appendingPathComponent("backup.zip")
do {
try createZip(from: sourceFolder, to: archiveURL)
print("Архив создан: \(archiveURL.path)")
} catch {
print("Ошибка: \(error)")
}
Пример 2: Распаковка ZIP-архива
import ZIPFoundation
func unzipArchive(at sourceURL: URL, to destinationURL: URL) throws {
let fileManager = FileManager()
// Создаём папку назначения, если её нет
try fileManager.createDirectory(at: destinationURL,
withIntermediateDirectories: true)
// Распаковываем
try fileManager.unzipItem(at: sourceURL, to: destinationURL)
}
// Использование
let archiveURL = documents.appendingPathComponent("backup.zip")
let extractFolder = documents.appendingPathComponent("ExtractedData")
do {
try unzipArchive(at: archiveURL, to: extractFolder)
print("Распаковка завершена")
} catch {
print("Ошибка распаковки: \(error)")
}
Пример 3: Работа с отдельными файлами в архиве
import ZIPFoundation
func listFilesInArchive(at archiveURL: URL) throws -> [String] {
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
throw NSError(domain: "ZIPError", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Не удалось открыть архив"])
}
var fileNames: [String] = []
for entry in archive {
fileNames.append(entry.path)
print("Файл: \(entry.path), размер: \(entry.uncompressedSize) байт")
}
return fileNames
}
// Извлечение конкретного файла
func extractSpecificFile(from archiveURL: URL,
fileName: String,
to destinationURL: URL) throws {
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
throw NSError(domain: "ZIPError", code: -1,
userInfo: nil)
}
guard let entry = archive[fileName] else {
throw NSError(domain: "ZIPError", code: -2,