【问题标题】:DownloadTask gets paused while executing as part of the Background Fetch, if app is not in the foreground?如果应用程序不在前台,DownloadTask 在作为后台获取的一部分执行时会暂停?
【发布时间】:2019-02-10 18:14:45
【问题描述】:

我需要定期执行一项任务,确切地说是每天一次,因此我使用 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)
    }


}

【问题讨论】:

  • @Rob 谢谢,我知道我无法安排在确切时间发生的事情,我只想提一下,这将是理想的情况。对于可能解决它的完成处理程序,如果我将操作系统提供的一个变量保存在一个变量中,然后在需要时使用该变量调用它,它会起作用吗?
  • @Rob 这就是我进行会话的方式private lazy var urlSession: URLSession = { let config = URLSessionConfiguration.background(withIdentifier: "MySession") //config.isDiscretionary = true config.sessionSendsLaunchEvents = true return URLSession(configuration: config, delegate: self, delegateQueue: nil) }()
  • @Rob 当我保存并使用提供的 completionHandler 时它起作用了!非常感谢你!我还将检查后台获取和后台会话之间的区别,我认为那里也可能存在问题,因为我没有太多经验:)。再次感谢。你也可以把它写成这个问题的答案。

标签: ios swift background-fetch


【解决方案1】:

一些想法:

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

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

    如果您要执行基于委托的 URLSession,您应该将其完成处理程序保存在您自己的 ivar 中并在应用仍在后台运行时调用它(在 30 秒内)。

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

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

【讨论】:

  • 我保存并使用操作系统提供的完成处理程序后问题解决了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-15
  • 2013-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-15
相关资源
最近更新 更多