Работа с ZIP/архивами в Swift (iOS/macOS): полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Работа с 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                    

Рекомендаций