Загрузка с прогрессом и отменой в iOS

Реализация загрузки файлов с отображением прогресса и возможностью отмены – распространенная задача при разработке приложений для iOS. Существует несколько подходов, зависящих от используемого API (URLSession) и требуемой степени контроля над процессом. Рассмотрим наиболее эффективные методы.

1. Использование URLSession

URLSession предоставляет мощный механизм для сетевых операций, включая загрузку файлов. Для реализации прогресса используется делегирование URLSessionDownloadDelegate.

Основные шаги:

  1. Создание сессии: Создайте экземпляр URLSession с конфигурацией, подходящей для вашей задачи (например, default, ephemeral, background).
  2. Инициация загрузки: Используйте метод downloadTask(with:) для создания задачи загрузки. Этот метод принимает URL файла и возвращает объект URLSessionDownloadTask.
  3. Реализация делегата: Реализуйте протокол URLSessionDownloadDelegate в вашем классе. Ключевые методы:
    • urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData data: Data, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) – вызывается при получении новых данных. Здесь вычисляете и обновляете прогресс загрузки.
    • urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo url: URL) – вызывается после завершения загрузки файла. Здесь перемещаете файл в нужное место хранения.
    • urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didCancelWithError error: Error?) – вызывается при отмене задачи загрузки или возникновении ошибки.
  4. Запуск задачи: Вызовите метод resume() для объекта URLSessionDownloadTask, чтобы начать загрузку.
  5. Отмена задачи: Вызовите метод cancel() для объекта URLSessionDownloadTask, чтобы отменить загрузку.

Пример кода (упрощенный):

import Foundation

class FileDownloader: NSObject, URLSessionDelegate {
    private var downloadTask: URLSessionDownloadTask?
    var progressHandler: ((Double) -> Void)? // Closure для обновления прогресса
    var completionHandler: ((URL?, Error?) -> Void)? // Closure для завершения загрузки

    func startDownload(url: URL) {
        let sessionConfig = URLSessionConfiguration.default
        let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main)

        downloadTask = session.downloadTask(with: url) { (url, response, error) in
            self.completionHandler?(url, error)
        }

        downloadTask?.resume()
    }

    func cancelDownload() {
        downloadTask?.cancel()
    }

    // MARK: - URLSessionDelegate methods

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData data: Data, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        if let expected = totalBytesExpectedToWrite as? Int64, expected > 0 {
            let progress = Double(totalBytesWritten) / Double(expected)
            progressHandler?(progress)
        } else {
            // Если общий размер файла неизвестен, можно показывать индикатор неопределенного прогресса.
            progressHandler?(0.5) // Пример: отображение 50% прогресса
        }
    }

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo url: URL) {
        completionHandler?(url, nil)
    }

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didCancelWithError error: Error?) {
        completionHandler?(nil, error)
    }
}

2. Использование URLSessionStreamDelegate (для потоковой загрузки)

Если требуется обрабатывать данные по мере их поступления без полной загрузки в память, можно использовать URLSessionStreamDelegate. Этот подход полезен для больших файлов или потокового воспроизведения. Реализация прогресса аналогична URLSessionDownloadDelegate, но вместо получения полного файла вы получаете данные небольшими порциями через метод urlSession(_ session: URLSessionStreamTask, didBecomeInputStream input: InputStream).

3. Отображение прогресса в UI

Для отображения прогресса загрузки можно использовать различные элементы UI:

  • UIProgressView: Простой и удобный способ отображения линейного прогресса.
  • Custom View: Создание собственного представления для более гибкого управления внешним видом и функциональностью (например, круговой индикатор прогресса).

Важно обновлять UI в главном потоке (OperationQueue.main) во избежание проблем с многопоточностью.

4. Обработка ошибок

Необходимо тщательно обрабатывать возможные ошибки при загрузке файлов:

  • Сетевые ошибки: Отсутствие подключения к сети, таймауты, DNS-ошибки и т.д.
  • Ошибки файловой системы: Недостаточно места на диске, проблемы с правами доступа.
  • Ошибки сервера: HTTP-ошибки (404 Not Found, 500 Internal Server Error).

Предоставляйте пользователю информативные сообщения об ошибках и возможность повторить загрузку.

5. Дополнительные соображения

  • Фоновая загрузка: Для длительных загрузок используйте фоновые задачи URLSession (background tasks) чтобы не блокировать основной поток приложения.
  • Возобновляемые загрузки: Реализуйте механизм возобновления загрузки в случае прерывания соединения или перезапуска приложения. Это требует сохранения информации о текущем прогрессе и использования заголовков Range при повторной отправке запроса.
  • Многопоточность: Используйте GCD (Grand Central Dispatch) для выполнения сетевых операций в отдельных потоках, чтобы не блокировать основной поток UI.

Таблица сравнения подходов

ПодходПреимуществаНедостаткиПрименение
URLSessionDownloadDelegateПростота реализации, автоматическая обработка загрузки файла.Загружает весь файл в память перед сохранением.Небольшие и средние файлы, когда не требуется потоковая обработка.
URLSessionStreamDelegateПотоковая загрузка, минимальное использование памяти.Более сложная реализация, требует ручной обработки данных.Большие файлы, потоковое воспроизведение, когда требуется обрабатывать данные по мере поступления.

В заключение, выбор конкретного подхода зависит от требований вашего приложения и размера загружаемых файлов. URLSessionDownloadDelegate является наиболее простым решением для большинства случаев, в то время как URLSessionStreamDelegate предоставляет больше контроля над процессом загрузки, но требует более сложной реализации. Важно помнить об обработке ошибок и отображении прогресса загрузки для обеспечения удобного пользовательского опыта.


Будет интересно

Nikulux в Telegram

Статьи, архивы материалов, скоро ещё и мемы

(= Подписаться =)