Быстрое уведомление iOS после завершения загрузки с использованием URLSession

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

Уведомление работает отлично, когда приложение запущено. Но не работает, когда приложение переходит в фоновый режим. Когда приложение снова выходит на передний план, запускается код уведомления.

Мои коды:

import Foundation
import Zip
import UserNotifications

class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {

    static var shared = DownloadManager()
    var selectedBook: Book!

    typealias ProgressHandler = (Float, Float, Float) -> ()

    var onProgress : ProgressHandler? {
        didSet {
            if onProgress != nil {
                let _ = activate()
            }
        }
    }

    override private init() {
        super.init()
    }

    func activate() -> URLSession {
        let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background")

        // Warning: If an URLSession still exists from a previous download, it doesn't create a new URLSession object but returns the existing one with the old delegate object attached!
        return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
    }

    private func calculateProgress(session : URLSession, completionHandler : @escaping (Float, Float, Float) -> ()) {
        session.getTasksWithCompletionHandler { (tasks, uploads, downloads) in
            let progress = downloads.map({ (task) -> Float in
                if task.countOfBytesExpectedToReceive > 0 {
                    return Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive)
                } else {
                    return 0.0
                }
            })
            let countOfBytesReceived = downloads.map({ (task) -> Float in
                return Float(task.countOfBytesReceived)
            })
            let countOfBytesExpectedToReceive = downloads.map({ (task) -> Float in
                return Float(task.countOfBytesExpectedToReceive)
            })
            if progress.reduce(0.0, +) == 1.0 {
                self.postNotification()
            }
            completionHandler(progress.reduce(0.0, +), countOfBytesReceived.reduce(0.0, +), countOfBytesExpectedToReceive.reduce(0.0, +))
        }
    }

    func postUnzipProgress(progress: Double) {
        NotificationCenter.default.post(name: .UnzipProgress, object: progress)
    }

    func postNotification() {
        let center = UNUserNotificationCenter.current()
        center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
            // Enable or disable features based on authorization.
        }

        let content = UNMutableNotificationContent()
        content.title = NSString.localizedUserNotificationString(forKey: "Download Completed", arguments: nil)
        content.body = NSString.localizedUserNotificationString(forKey: "Quran Touch app is ready to use", arguments: nil)
        content.sound = UNNotificationSound.default()
        content.categoryIdentifier = "com.qurantouch.qurantouch"
        // Deliver the notification in 60 seconds.
        let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 2.0, repeats: false)
        let request = UNNotificationRequest.init(identifier: "downloadCompleted", content: content, trigger: trigger)

        // Schedule the notification.
        center.add(request)
    }

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {

        if totalBytesExpectedToWrite > 0 {
            if let onProgress = onProgress {
                calculateProgress(session: session, completionHandler: onProgress)
            }
            let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
            debugPrint("Progress \(downloadTask) \(progress)")

        }
    }

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        debugPrint("Download finished: \(location)")

        let folder = URL.createFolder(folderName: selectedBook.folder)
        let fileURL = folder!.appendingPathComponent("ClipsAndTacksF1ForModeler.zip")

        if let url = URL.getFolderUrl(folderName: selectedBook.folder) {
            do {
                try FileManager.default.moveItem(at: location, to: fileURL)
                try Zip.unzipFile((fileURL), destination: url, overwrite: true, password: nil, progress: { (progress) -> () in
                    self.postUnzipProgress(progress: progress)
                    if progress == 1 {
//                        self.postNotification()
                        UserDefaults.standard.set("selected", forKey: self.selectedBook.fileKey)
                        URL.removeFile(file: fileURL)
                    }
                }, fileOutputHandler: {(outputUrl) -> () in
                })
            } catch {
                print(error)
            }
        }
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        debugPrint("Task completed: \(task), error: \(error)")
    }

}

И начал загрузку здесь

func downloadBookWithUrl(url: String) {
        DownloadManager.shared.selectedBook = selectedBook
        let url = URL(string: url)!
        let task = DownloadManager.shared.activate().downloadTask(with: url)
        task.resume()        
    }

Я получил пример от Apple, написанный на Objective C. Но не смог его пройти, так как не знаю Objective C. Вот пример: https://developer.apple.com/library/archive/samplecode/SimpleBackgroundTransfer/Introduction/Intro.html#//apple_ref/doc/uid/DTS40013416

Вы читали статью Загрузка файлов в фоновом режиме от Apple?

subdan 02.04.2019 10:56

спасибо @subdan за ссылки. Я прошел через них и решил проблему. Ответил и на мой вопрос.

Ahsan Aasim 02.04.2019 11:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
1 165
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я следовал инструкции от Документы Apple, предложенные субданом в комментариях

Реализован метод handleEventsForBackgroundURLSession в appDelegate. И прежде чем показать уведомление, я вызвал обработчик завершения из appDelegate, и он заработал.

В делегате приложения:

var backgroundCompletionHandler: (() -> Void)?
func application(_ application: UIApplication,
                     handleEventsForBackgroundURLSession identifier: String,
                     completionHandler: @escaping () -> Void) {
        backgroundCompletionHandler = completionHandler
    }

И перед вызовом уведомления:

DispatchQueue.main.async {
                            guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
                                let backgroundCompletionHandler =
                                appDelegate.backgroundCompletionHandler else {
                                    return
                            }
                            backgroundCompletionHandler()
                            self.postNotification()
                        }

Другие вопросы по теме