ios - 如果应用程序不在前台,DownloadTask 在作为后台获取的一部分执行时会暂停吗?

标签 ios swift background-fetch

我需要定期执行任务,准确地说是一天一次,因此我使用 23 小时的 minimumBackgroundFetchInterval 实现了后台提取。当我在前台使用我的应用程序模拟后台获取时,它就完成了。

但是当我的应用程序在后台时,只有 application(_ application:, performFetchWithCompletionHandler) 方法被调用,而 urlSession(_ session:, downloadTask:, didFinishDownloadingTo:) 方法要么根本没有被调用,要么被调用然后在执行过程中的某个随机点暂停。当应用程序返回前台时,它会继续执行。

这在模拟器和设备上都会发生。

我的代码如下,具有上述两个功能。

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)
    }


}

最佳答案

几个想法:

  • “我需要定期执行一项任务,准确地说是一天一次”……我知道这就是您想要的,但操作系统规定了频率(基于用户启动您的设备的频率)应用程序,新数据出现的频率等)。如果您需要在特定时间发生某些事情,您可能必须考虑推送通知(这实际上也不是为此目的而设计的)。

  • 我看到您用自己的 block 定义了自己的 completionHandler 变量。这不是它的工作原理。您必须让 performFetchWithCompletionHandler 保存操作系统提供给您的完成处理程序,然后调用它。您永远不会调用他们的完成处理程序闭包,因此,您不会参与 future 的后台获取。

    如果你打算做一个基于委托(delegate)的 URLSession,你应该在你自己的 ivar 中保存他们的完成处理程序并调用它(在 30 秒内),而应用程序仍在运行背景。

  • 在您的评论中,您提到 urlSession 是后台 URLSession。这是一种完全不同的机制(在您的应用程序暂停/终止时运行请求)不要与“后台获取”混淆,在这种情况下,您的应用程序被唤醒并且必须在您的应用程序再次暂停之​​前的 30 秒内完全唤醒。通常,您会使用非后台 URLSession 来获取数据(不是后台 URLSession,因为那样速度较慢)。

    现在,如果您要检索的数据太多以至于响应可能需要很长时间才能完成(超过 30 秒),只需使用这个初始的非后台提取来查看是否有要提取的数据,如果您想要,可以选择使用第二个后台 URLSession 来启动所有数据的获取。但这更复杂,所以我只会在你认为你不能在 30 秒内合理地完成所有数据的获取时这样做。

关于ios - 如果应用程序不在前台,DownloadTask 在作为后台获取的一部分执行时会暂停吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54619493/

相关文章:

ios - 快速从用户向用户发送推送通知 Firebase

Swift: "Cannot invoke subscript":通过在索引处传递类的数组来设置对象成员

IOS - 使后台获取每天只发生一次(不需要特定时间)

iphone - XCode (iOS) 中委托(delegate)的自动导入回调

ios - UITextView setContentOffset with scrollEnabled=NO in iOS7

ios - 使用UIToolbar/UITextField放弃第一响应者问题

ios - 如何将分隔符后的两位数字转换为字符串?

ios - Swift - 标签角在tableviewcell中有不同的半径

ios - 是否适合用于后台获取?

ios - 后台获取的替代方案