Vous avez fait un grand nombre des étapes nécessaires:
Cela dit, quelques observations:
je vérifier les autorisations pour l'application dans "Paramètres" » "Général"» "Background App Refresh". Cela garantit que non seulement vous avez réussi à demander l'extraction d'arrière-plan dans votre plist, mais que c'est activé en général, ainsi que pour votre application en particulier.
Assurez-vous que vous ne tuez pas l'application (c.-à-d.en appuyant deux fois sur le bouton d'accueil et en glissant sur votre application pour forcer l'application à se terminer). Si l'application est supprimée, elle empêchera l'extraction en arrière-plan de fonctionner correctement.
Vous utilisez debugPrint
, mais cela ne fonctionne que lorsque vous l'exécutez à partir de Xcode. Mais vous devriez le faire sur un périphérique physique, pas à partir de Xcode. Vous devez utiliser un système de journalisation qui vous montre l'activité même lorsque vous ne lancez pas l'application via Xcode.
J'utilise os_log
et regarde à partir de la console (voir WWDC 2016 Unified Logging and Activity Tracing) ou depuis la notification par le UserNotifications framework (voir WWDC 2016 Introduction to Notifications) donc je suis informé lorsque l'application fait quelque chose notable en arrière-plan. Ou j'ai créé mes propres systèmes de journalisation externes (par exemple en écrivant dans un fichier texte ou plist). Mais vous avez besoin d'un moyen d'observer l'activité en dehors de print
/debugPrint
parce que vous voulez tester ceci sans l'exécuter indépendamment de Xcode. Tous les comportements liés à l'arrière-plan changent lors de l'exécution d'une application connectée au débogueur.
En tant que PGDev said, vous n'avez aucun contrôle sur le moment où l'extraction d'arrière-plan a lieu. Il prend en compte de nombreux facteurs mal documentés (connectivité wifi, alimentation connectée, fréquence d'utilisation de l'application par l'utilisateur, moment où d'autres applications tournent, etc.). Cela étant dit, quand j'ai activé l'extraction en arrière-plan, a couru l'application de l'appareil (pas Xcode), et l'avait connecté au wifi et à l'alimentation, le premier fond appelé apparaissait sur mon iPhone 7+ dans les 10 minutes de suspendre l'application.
Votre code ne fait actuellement aucune demande de récupération. Cela soulève deux préoccupations:
Assurez-vous que l'application de test émet en fait URLSession
demande à un moment donné le cours normal de l'action lorsque vous l'exécutez (à savoir lorsque vous exécutez l'application normalement, pas par fond chercher). Si vous avez une application de test qui n'émet aucune requête, elle ne semble pas activer la fonctionnalité d'extraction en arrière-plan. (À tout le moins, cela affecte gravement la fréquence des demandes d'extraction d'arrière-plan.)
Selon les informations reçues, le système d'exploitation cessera d'émettre des appels de récupération d'arrière-plan ultérieurs vers votre application si les appels d'arrière-plan antérieurs n'ont pas réellement abouti demande de réseau émise. (Cela peut être une permutation du point précédent, ce n'est pas tout à fait clair.) Je soupçonne qu'Apple essaye d'empêcher les développeurs d'utiliser le mécanisme d'extraction d'arrière-plan pour les tâches qui ne récupèrent rien.
Remarque, votre application n'a pas beaucoup de temps pour effectuer la demande, donc si vous émettez une demande, vous voudrez peut-être demander uniquement si des données sont disponibles, mais pas essayer de télécharger toutes les données lui-même. Vous pouvez ensuite lancer une session en arrière-plan pour démarrer les téléchargements fastidieux. Évidemment, si la quantité de données récupérées est négligeable, il est peu probable que cela soit un problème, mais assurez-vous de terminer votre requête en appelant l'exécution de l'arrière-plan assez rapidement (30 secondes, IIRC). Si vous ne l'appelez pas dans ce délai, cela affectera si/quand les demandes de récupération d'arrière-plan suivantes sont tentées.
Si l'application ne traite pas les demandes d'arrière-plan, je suggère de supprimer l'application de l'appareil et de la réinstaller. J'ai eu une situation où, en testant l'extraction d'arrière-plan où les demandes ont cessé de fonctionner (peut-être à la suite d'une demande d'extraction d'arrière-plan échouée lors du test d'une itération précédente de l'application). Je trouve que le retirer et le réinstaller est un bon moyen de réinitialiser le processus d'extraction d'arrière-plan.
Pour l'amour d'illustration, voici un exemple qui effectue fond va chercher avec succès. J'ai aussi ajouté cadre de UserNotifications et os_log
appels à fournir un moyen de suivre les progrès lorsqu'ils ne sont pas connectés à Xcode (là où print
et debugPrint
ne sont plus utiles):
// AppDelegate.swift
import UIKit
import UserNotifications
import os.log
@UIApplicationMain
class AppDelegate: UIResponder {
var window: UIWindow?
/// The URLRequest for seeing if there is data to fetch.
fileprivate var fetchRequest: URLRequest {
// create this however appropriate for your app
var request: URLRequest = ...
return request
}
/// A `OSLog` with my subsystem, so I can focus on my log statements and not those triggered
/// by iOS internal subsystems. This isn't necessary (you can omit the `log` parameter to `os_log`,
/// but it just becomes harder to filter Console for only those log statements this app issued).
fileprivate let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "log")
}
// MARK: - UIApplicationDelegate
extension AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// turn on background fetch
application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
// issue log statement that app launched
os_log("didFinishLaunching", log: log)
// turn on user notifications if you want them
UNUserNotificationCenter.current().delegate = self
return true
}
func applicationWillEnterForeground(_ application: UIApplication) {
os_log("applicationWillEnterForeground", log: log)
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
os_log("performFetchWithCompletionHandler", log: log)
processRequest(completionHandler: completionHandler)
}
}
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
os_log("willPresent %{public}@", log: log, notification)
completionHandler(.alert)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping() -> Void) {
os_log("didReceive %{public}@", log: log, response)
completionHandler()
}
}
// MARK: - Various utility methods
extension AppDelegate {
/// Issue and process request to see if data is available
///
/// - Parameters:
/// - prefix: Some string prefix so I know where request came from (i.e. from ViewController or from background fetch; we'll use this solely for logging purposes.
/// - completionHandler: If background fetch, this is the handler passed to us by`performFetchWithCompletionHandler`.
func processRequest(completionHandler: ((UIBackgroundFetchResult) -> Void)? = nil) {
let task = URLSession.shared.dataTask(with: fetchRequest) { data, response, error in
// since I have so many paths execution, I'll `defer` this so it captures all of them
var result = UIBackgroundFetchResult.failed
var message = "Unknown"
defer {
self.postNotification(message)
completionHandler?(result)
}
// handle network errors
guard let data = data, error == nil else {
message = "Network error: \(error?.localizedDescription ?? "Unknown error")"
return
}
// my web service returns JSON with key of `success` if there's data to fetch, so check for that
guard
let json = try? JSONSerialization.jsonObject(with: data),
let dictionary = json as? [String: Any],
let success = dictionary["success"] as? Bool else {
message = "JSON parsing failed"
return
}
// report back whether there is data to fetch or not
if success {
result = .newData
message = "New Data"
} else {
result = .noData
message = "No Data"
}
}
task.resume()
}
/// Post notification if app is running in the background.
///
/// - Parameters:
///
/// - message: `String` message to be posted.
func postNotification(_ message: String) {
// if background fetch, let the user know that there's data for them
let content = UNMutableNotificationContent()
content.title = "MyApp"
content.body = message
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
// for debugging purposes, log message to console
os_log("%{public}@", log: self.log, message) // need `public` for strings in order to see them in console ... don't log anything private here like user authentication details or the like
}
}
Et le contrôleur de vue demande simplement l'autorisation de notifications utilisateur et émet une requête aléatoire:
import UIKit
import UserNotifications
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// request authorization to perform user notifications
UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .alert]) { granted, error in
if !granted {
DispatchQueue.main.async {
let alert = UIAlertController(title: nil, message: "Need notification", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
// you actually have to do some request at some point for background fetch to be turned on;
// you'd do something meaningful here, but I'm just going to do some random request...
let url = URL(string: "http://example.com")!
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
let alert = UIAlertController(title: nil, message: error?.localizedDescription ?? "Sample request finished", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true)
}
}
task.resume()
}
}
Oui, activé Extraction en arrière-plan; Paramétrage également effectué, autorisation d'autorisation, l'application est à l'arrière-plan – user6539552