iosswiftbackground-fetch

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


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


}

Solution

  • A couple of thoughts: