DownloadTask приостанавливается при выполнении как часть Background Fetch, если приложение не на переднем плане? - PullRequest
0 голосов
/ 10 февраля 2019

Мне нужно выполнять задачу периодически, если быть точным, раз в день, поэтому я реализовал фоновую выборку с 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 Ответ

0 голосов
/ 10 февраля 2019

Пара мыслей:

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

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

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

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

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

...