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 секунд.