DownloadTask приостанавливается при выполнении как часть Background Fetch, если приложение не на переднем плане?

Мне нужно выполнять задачу периодически, точнее раз в день, поэтому я реализовал фоновую выборку с minimumBackgroundFetchInterval 23 часа. Это делается, когда я симулирую фоновую выборку с моим приложением на переднем плане.

Но когда мое приложение находится в фоновом режиме, только 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)
    }


}

1 ответ

Решение

Пара мыслей:

  • "Мне нужно выполнять задачу периодически, если быть точным, раз в день"... Я понимаю, что вы этого хотите, но ОС определяет частоту (на основе того, как часто пользователь запускает ваше приложение, как часто появляются новые данные и т. д.). Если вам нужно, чтобы что-то произошло в определенное время, вам, возможно, придется рассмотреть push-уведомления (которые на самом деле также не предназначены для этой цели).

  • Я вижу, что вы определили свои собственные completionHandler переменная с вашим собственным блоком. Это не так, как это работает. Вы должны иметь performFetchWithCompletionHandler сохраните обработчик завершения, предоставленный вам ОС, а затем вызовите его. Вы никогда не вызываете закрытие их обработчика завершения, и в результате вы не будете участвовать в будущих фоновых выборках.

    Если вы собираетесь сделать делегат на основе URLSessionВы должны сохранить обработчик их завершения в своем собственном иваре и вызвать его (в течение 30 секунд), пока приложение все еще работает в фоновом режиме.

  • В ваших комментариях вы упоминаете, что urlSession это фон URLSession, Это совершенно другой механизм (для запуска запросов, когда ваше приложение приостановлено / прекращено), его не следует путать с "фоновой выборкой", в этом случае ваше приложение пробуждается и должно полностью завершиться в течение 30 секунд, прежде чем ваше приложение будет снова приостановлено. Как правило, вы бы использовали без фона URLSession получить данные (не фон URLSession, потому что это медленнее).

    Теперь, если вы извлекаете так много данных, что этот ответ может занять слишком много времени для завершения (более 30 секунд), просто используйте эту начальную не фоновую выборку, только чтобы увидеть, есть ли данные для выборки, и, если вы хотите, опционально использовать второй фон URLSession начать выборку всех данных. Но это более сложно, поэтому я бы сделал это только в том случае, если вы не думаете, что сможете завершить выборку всех данных за 30 секунд.

Другие вопросы по тегам