我正在使用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()
}
我从苹果公司得到了一个用目标C编写的示例。但是由于我不了解目标C,所以无法通过它。 这是示例:https://developer.apple.com/library/archive/samplecode/SimpleBackgroundTransfer/Introduction/Intro.html#//apple_ref/doc/uid/DTS40013416
答案 0 :(得分:0)
我遵循了Apple docs suggested by subdan in comments
的指示在appDelegate中说明了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()
}