DownloadTask gets paused while executing as part of the Background Fetch, if app is not in the foreground?

陌路散爱 提交于 2020-06-28 20:25:44

问题


I need to perform a task periodically, once a day to be exact, so I implemented background fetch with minimumBackgroundFetchInterval of 23 hours. It gets done when I simulate background fetch with my app in the foreground.

But when my app is in the background only the application(_ application:, performFetchWithCompletionHandler) method gets called like it should be, and the urlSession(_ session:, downloadTask:, didFinishDownloadingTo:) method either doesn't get called at all or gets called and then paused at some random point in execution.When app gets back in the foreground it continues executing.

This happens both on simulator and on a device.

My code is below, with both of the above mentioned functions.

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


}

回答1:


A couple of thoughts:

  • “I need to perform a task periodically, once a day to be exact” ... I understand that’s what you want, but the OS dictates the frequency (on the basis of how often the user fires up your app, how often there’s new data, etc.). If you need something to happen at a particular time, you may have to consider push notifications (which really isn’t intended for this purpose, either).

  • I see that you’ve defined your own completionHandler variable with your own block. That’s not how it works. You must have performFetchWithCompletionHandler save the completion handler that the OS provided to you, and then call that. You’re never calling their completion handler closure, and as a result, you won’t participate in future background fetch.

    If you’re going to do a delegate-based URLSession, you should save their completion handler in your own ivar and call it (within 30 seconds), while the app is still running in the background.

  • In your comments, you mention that urlSession is a background URLSession. That’s a completely different mechanism (to run requests while your app is suspended/terminated) not to be confused with “background fetch”, in which case your app is awaken and must completely within 30 seconds before your app is suspended again. Typically you’d use a non-background URLSession to fetch the data (not background URLSession, because that is slower).

    Now, if you’re retrieving so much data that that response might take too long to finish (more than 30 seconds), just use this initial non-background fetch only to see if there is data to fetch, and if you want, optionally use a second, background URLSession to initiate the fetch of all the data. But that’s more complicated, so I’d only do that if you don’t think you can reasonably finish the fetch of all the data in 30 seconds.



来源:https://stackoverflow.com/questions/54619493/downloadtask-gets-paused-while-executing-as-part-of-the-background-fetch-if-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!