Работа с ZIP/архивами в Swift (iOS/macOS)
В современной iOS и macOS разработке часто возникает необходимость упаковывать и распаковывать файлы. ZIP-архивы — стандартный способ сжатия данных, поддерживаемый на всех платформах Apple. В этой статье мы рассмотрим нативные средства Swift для работы с ZIP, а также популярную библиотеку ZIPFoundation.
Нативные средства Foundation
Фреймворк Foundation предоставляет базовые возможности для работы с архивами через класс NSFileCoordinator и системные утилиты. Однако для полноценной работы с ZIP рекомендуется использовать сторонние библиотеки или обёртки над системными командами.
Библиотека ZIPFoundation
ZIPFoundation — это легковесная библиотека для работы с ZIP-архивами, написанная на чистом Swift. Она поддерживает iOS, macOS, tvOS и watchOS, не требует внешних зависимостей и распространяется под лицензией MIT.
Установка через Swift Package Manager
Добавьте зависимость в Package.swift:
dependencies: [
.package(url: "https://github.com/weichsel/ZIPFoundation.git", from: "0.9.16")
]
Или через Xcode: File → Add Packages → введите URL репозитория.
Основные операции
1. Создание ZIP-архива
Упаковка одного файла:
import ZIPFoundation
let fileManager = FileManager.default
let sourceURL = URL(fileURLWithPath: "/path/to/file.txt")
let destinationURL = URL(fileURLWithPath: "/path/to/archive.zip")
do {
try fileManager.zipItem(at: sourceURL, to: destinationURL)
print("Архив создан: \(destinationURL.path)")
} catch {
print("Ошибка: \(error.localizedDescription)")
}
Упаковка целой директории:
let directoryURL = URL(fileURLWithPath: "/path/to/folder")
let archiveURL = URL(fileURLWithPath: "/path/to/folder.zip")
do {
try fileManager.zipItem(at: directoryURL, to: archiveURL)
print("Директория упакована")
} catch {
print("Ошибка: \(error)")
}
2. Распаковка архива
let archiveURL = URL(fileURLWithPath: "/path/to/archive.zip")
let destinationURL = URL(fileURLWithPath: "/path/to/unzip")
do {
try fileManager.unzipItem(at: archiveURL, to: destinationURL)
print("Распаковка завершена")
} catch {
print("Ошибка распаковки: \(error)")
}
3. Работа с содержимым архива без извлечения
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
fatalError("Не удалось открыть архив")
}
// Список всех записей
for entry in archive {
print("Файл: \(entry.path), размер: \(entry.uncompressedSize) байт")
}
// Чтение конкретного файла
if let entry = archive["documents/report.pdf"] {
var data = Data()
do {
_ = try archive.extract(entry, bufferSize: 4096) { partialData in
data.append(partialData)
}
print("Извлечено \(data.count) байт")
} catch {
print("Ошибка: \(error)")
}
}
4. Добавление файлов в существующий архив
guard let archive = Archive(url: archiveURL, accessMode: .update) else {
fatalError("Не удалось открыть архив для обновления")
}
let newFileURL = URL(fileURLWithPath: "/path/to/new_file.txt")
do {
try archive.addEntry(with: "new_file.txt", relativeTo: newFileURL.deletingLastPathComponent())
print("Файл добавлен")
} catch {
print("Ошибка: \(error)")
}
Работа с прогрессом и большими файлами
Для больших архивов используйте Progress:
let progress = Progress(totalUnitCount: 100)
let archiveURL = URL(fileURLWithPath: "/path/to/large.zip")
let destinationURL = URL(fileURLWithPath: "/path/to/output")
do {
try FileManager.default.unzipItem(at: archiveURL, to: destinationURL,
progress: progress)
// Подписка на изменения прогресса
progress.observe(\.fractionCompleted) { observedProgress, _ in
DispatchQueue.main.async {
print("Прогресс: \(Int(observedProgress.fractionCompleted * 100))%")
}
}
} catch {
print("Ошибка: \(error)")
}
Сжатие с паролем
ZIPFoundation поддерживает шифрование через AESEncryptor:
guard let archive = Archive(url: archiveURL, accessMode: .create) else {
fatalError("Не удалось создать архив")
}
let fileURL = URL(fileURLWithPath: "/path/to/secret.txt")
do {
try archive.addEntry(with: "secret.txt",
relativeTo: fileURL.deletingLastPathComponent(),
compressionMethod: .deflate,
encrypt