如果应用程序不在前台,DownloadTask 在作为后台获取的一部分执行时会暂停吗?
DownloadTask gets paused while executing as part of the Background Fetch, if app is not in the foreground?
我需要定期执行一项任务,准确地说是一天一次,因此我实现了 minimumBackgroundFetchInterval
23 小时的后台提取。当我在前台使用我的应用程序模拟后台获取时,它就完成了。
但是当我的应用程序在后台时,只有 application(_ application:, performFetchWithCompletionHandler)
方法被调用,而 urlSession(_ session:, downloadTask:, didFinishDownloadingTo:)
方法要么根本没有被调用,要么被调用然后暂停在 execution.When 中的某个随机点,应用程序返回到前台并继续执行。
这在模拟器和设备上都会发生。
我的代码如下,具有上述两个功能。
var sviBrojevi = EncodingKontakt(provjeri: [])
var completionHandler: (UIBackgroundFetchResult) -> Void = { result in
return
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
let container = persistentContainer
let context = container.viewContext
sviBrojevi = EncodingKontakt(provjeri: [])
let request: NSFetchRequest<TelefonskiBroj> = TelefonskiBroj.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "ime", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))]
do{
let matches = try context.fetch(request)
if matches.count > 0 {
for match in matches{
sviBrojevi.provjeri.append(FetchedContact(ime: match.ime!, brojevi: [match.broj!]))
}
}
}catch {
print("Could not load data!")
}
guard let url = URL(string: "") else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("", forHTTPHeaderField: "Authorization")
let data = try? JSONEncoder().encode(sviBrojevi)
urlRequest.httpBody = data
let backgroundtask = urlSession.downloadTask(with: urlRequest)
backgroundtask.resume()
}
var numberOfContactsChanged = 0
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
var kontakti = DecodingKontakt(provjereno: [])
do{
let contents = try Data.init(contentsOf: location)
kontakti = try JSONDecoder().decode(DecodingKontakt.self, from: contents)
}catch let error{
print(error.localizedDescription)
completionHandler(.failed)
}
var promijenjeniBrojevi = [String]()
var brojac = 0
let sviKontakti = kontakti.provjereno
persistentContainer.performBackgroundTask { [weak self] (context) in
for index in sviKontakti.indices{
let contact = sviKontakti[index]
let number = self!.sviBrojevi.provjeri[index].brojevi[0] //GRESKA
let request: NSFetchRequest<TelefonskiBroj> = TelefonskiBroj.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "ime", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))]
request.predicate = NSPredicate(format: "ime = %@ AND broj = %@", contact.ime, number)
// request.returnsObjectsAsFaults = false
do{
let match = try context.fetch(request)
if match.count > 0 {
assert(match.count == 1, "AppDelegate.urlSession -- database inconsistency")
if match[0].operater != contact.brojevi[0]{//, match[0].operater != nil{
let obavjestenje = Obavjestenja(context: context)
obavjestenje.broj = number
obavjestenje.datumPromjene = Date()
obavjestenje.stariOperator = match[0].operater
obavjestenje.noviOperator = contact.brojevi[0]
obavjestenje.ime = match[0].ime
if let ime = match[0].ime {
promijenjeniBrojevi.append(ime)
}
let badgeNum = ImenikTableViewController.defaults.integer(forKey: "obavjestenja") + 1
ImenikTableViewController.defaults.set(badgeNum, forKey: "obavjestenja")
obavjestenje.sekcija = ""
brojac += 1
ImenikTableViewController.defaults.set(brojac, forKey: "obavjestenja")
}
match[0].operater = contact.brojevi[0]
match[0].vrijemeProvjere = Date()
}
}catch {
self?.completionHandler(.failed)
print("Could not load data!")
}
}
try? context.save()
if promijenjeniBrojevi.count > 0{
let center = UNUserNotificationCenter.current()
//create the content for the notification
let content = UNMutableNotificationContent()
content.title = "Operator"
content.sound = UNNotificationSound.default
content.badge = NSNumber(integerLiteral: promijenjeniBrojevi.count)
if promijenjeniBrojevi.count == 1{
content.body = "\(promijenjeniBrojevi[0]) je promijenio/la mrežu"
}else if promijenjeniBrojevi.count == 2{
content.body = "\(promijenjeniBrojevi[0]) i \(promijenjeniBrojevi[1]) su promijenili mrežu"
}else{
content.body = "\(promijenjeniBrojevi[0]) i drugi su promijenili mrežu"
}
//notification trigger can be based on time, calendar or location
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(5), repeats: false)
//create request to display
let request = UNNotificationRequest(identifier: "Obavjestenje", content: content, trigger: trigger)
//add request to notification center
center.add(request) { (error) in
if error != nil {
print("error \(String(describing: error))")
}
}
self?.completionHandler(.newData)
}
NotificationCenter.default.post(name: Notification.backFetch, object: nil)
}
}
几个想法:
“我需要定期执行一项任务,准确地说是一天一次”……我明白这就是你想要的,但是 OS 规定了频率(基于用户启动您的应用程序的频率、新数据出现的频率等)。如果您需要在特定时间发生某些事情,您可能必须考虑推送通知(这实际上也不是为此目的而设计的)。
我看到你用自己的块定义了自己的 completionHandler
变量。这不是它的工作原理。您必须 performFetchWithCompletionHandler
保存 OS 提供给您的完成处理程序,然后调用它。你永远不会调用他们的完成处理程序闭包,因此,你不会参与未来的后台获取。
如果你打算做一个基于委托的 URLSession
,你应该在你自己的 ivar 中保存他们的完成处理程序并调用它(在 30 秒内),而应用程序仍然 运行宁在后台。
在您的评论中,您提到 urlSession
是背景 URLSession
。这是一种完全不同的机制(在您的应用程序 suspended/terminated 时对 运行 请求)不要与“后台获取”混淆,在这种情况下,您的应用程序被唤醒并且必须在应用程序启动前 30 秒内完全唤醒再次暂停。通常你会使用非后台 URLSession
来获取数据(不是后台 URLSession
,因为那样比较慢)。
现在,如果您检索的数据太多以至于响应可能需要很长时间才能完成(超过 30 秒),只需使用此初始非后台提取来查看是否有数据要提取,并且如果需要,可以选择使用第二个后台 URLSession
来启动所有数据的获取。但这比较复杂,所以我只会在你认为你不能在 30 秒内合理地完成所有数据的获取时才这样做。
我需要定期执行一项任务,准确地说是一天一次,因此我实现了 minimumBackgroundFetchInterval
23 小时的后台提取。当我在前台使用我的应用程序模拟后台获取时,它就完成了。
但是当我的应用程序在后台时,只有 application(_ application:, performFetchWithCompletionHandler)
方法被调用,而 urlSession(_ session:, downloadTask:, didFinishDownloadingTo:)
方法要么根本没有被调用,要么被调用然后暂停在 execution.When 中的某个随机点,应用程序返回到前台并继续执行。
这在模拟器和设备上都会发生。
我的代码如下,具有上述两个功能。
var sviBrojevi = EncodingKontakt(provjeri: [])
var completionHandler: (UIBackgroundFetchResult) -> Void = { result in
return
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
let container = persistentContainer
let context = container.viewContext
sviBrojevi = EncodingKontakt(provjeri: [])
let request: NSFetchRequest<TelefonskiBroj> = TelefonskiBroj.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "ime", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))]
do{
let matches = try context.fetch(request)
if matches.count > 0 {
for match in matches{
sviBrojevi.provjeri.append(FetchedContact(ime: match.ime!, brojevi: [match.broj!]))
}
}
}catch {
print("Could not load data!")
}
guard let url = URL(string: "") else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("", forHTTPHeaderField: "Authorization")
let data = try? JSONEncoder().encode(sviBrojevi)
urlRequest.httpBody = data
let backgroundtask = urlSession.downloadTask(with: urlRequest)
backgroundtask.resume()
}
var numberOfContactsChanged = 0
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
var kontakti = DecodingKontakt(provjereno: [])
do{
let contents = try Data.init(contentsOf: location)
kontakti = try JSONDecoder().decode(DecodingKontakt.self, from: contents)
}catch let error{
print(error.localizedDescription)
completionHandler(.failed)
}
var promijenjeniBrojevi = [String]()
var brojac = 0
let sviKontakti = kontakti.provjereno
persistentContainer.performBackgroundTask { [weak self] (context) in
for index in sviKontakti.indices{
let contact = sviKontakti[index]
let number = self!.sviBrojevi.provjeri[index].brojevi[0] //GRESKA
let request: NSFetchRequest<TelefonskiBroj> = TelefonskiBroj.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "ime", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))]
request.predicate = NSPredicate(format: "ime = %@ AND broj = %@", contact.ime, number)
// request.returnsObjectsAsFaults = false
do{
let match = try context.fetch(request)
if match.count > 0 {
assert(match.count == 1, "AppDelegate.urlSession -- database inconsistency")
if match[0].operater != contact.brojevi[0]{//, match[0].operater != nil{
let obavjestenje = Obavjestenja(context: context)
obavjestenje.broj = number
obavjestenje.datumPromjene = Date()
obavjestenje.stariOperator = match[0].operater
obavjestenje.noviOperator = contact.brojevi[0]
obavjestenje.ime = match[0].ime
if let ime = match[0].ime {
promijenjeniBrojevi.append(ime)
}
let badgeNum = ImenikTableViewController.defaults.integer(forKey: "obavjestenja") + 1
ImenikTableViewController.defaults.set(badgeNum, forKey: "obavjestenja")
obavjestenje.sekcija = ""
brojac += 1
ImenikTableViewController.defaults.set(brojac, forKey: "obavjestenja")
}
match[0].operater = contact.brojevi[0]
match[0].vrijemeProvjere = Date()
}
}catch {
self?.completionHandler(.failed)
print("Could not load data!")
}
}
try? context.save()
if promijenjeniBrojevi.count > 0{
let center = UNUserNotificationCenter.current()
//create the content for the notification
let content = UNMutableNotificationContent()
content.title = "Operator"
content.sound = UNNotificationSound.default
content.badge = NSNumber(integerLiteral: promijenjeniBrojevi.count)
if promijenjeniBrojevi.count == 1{
content.body = "\(promijenjeniBrojevi[0]) je promijenio/la mrežu"
}else if promijenjeniBrojevi.count == 2{
content.body = "\(promijenjeniBrojevi[0]) i \(promijenjeniBrojevi[1]) su promijenili mrežu"
}else{
content.body = "\(promijenjeniBrojevi[0]) i drugi su promijenili mrežu"
}
//notification trigger can be based on time, calendar or location
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(5), repeats: false)
//create request to display
let request = UNNotificationRequest(identifier: "Obavjestenje", content: content, trigger: trigger)
//add request to notification center
center.add(request) { (error) in
if error != nil {
print("error \(String(describing: error))")
}
}
self?.completionHandler(.newData)
}
NotificationCenter.default.post(name: Notification.backFetch, object: nil)
}
}
几个想法:
“我需要定期执行一项任务,准确地说是一天一次”……我明白这就是你想要的,但是 OS 规定了频率(基于用户启动您的应用程序的频率、新数据出现的频率等)。如果您需要在特定时间发生某些事情,您可能必须考虑推送通知(这实际上也不是为此目的而设计的)。
我看到你用自己的块定义了自己的
completionHandler
变量。这不是它的工作原理。您必须performFetchWithCompletionHandler
保存 OS 提供给您的完成处理程序,然后调用它。你永远不会调用他们的完成处理程序闭包,因此,你不会参与未来的后台获取。如果你打算做一个基于委托的
URLSession
,你应该在你自己的 ivar 中保存他们的完成处理程序并调用它(在 30 秒内),而应用程序仍然 运行宁在后台。在您的评论中,您提到
urlSession
是背景URLSession
。这是一种完全不同的机制(在您的应用程序 suspended/terminated 时对 运行 请求)不要与“后台获取”混淆,在这种情况下,您的应用程序被唤醒并且必须在应用程序启动前 30 秒内完全唤醒再次暂停。通常你会使用非后台URLSession
来获取数据(不是后台URLSession
,因为那样比较慢)。现在,如果您检索的数据太多以至于响应可能需要很长时间才能完成(超过 30 秒),只需使用此初始非后台提取来查看是否有数据要提取,并且如果需要,可以选择使用第二个后台
URLSession
来启动所有数据的获取。但这比较复杂,所以我只会在你认为你不能在 30 秒内合理地完成所有数据的获取时才这样做。