我正在使用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()
}
我得到了一个用Objective C编写的苹果的例子。但由于我不知道Objective C而无法通过它。这是一个例子:https://developer.apple.com/library/archive/samplecode/SimpleBackgroundTransfer/Introduction/Intro.html#//apple_ref/doc/uid/DTS40013416
我按照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()
}