如果应用程序不在前台,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 秒内合理地完成所有数据的获取时才这样做。