J'ai essayé d'implémenter la récupération en arrière-plan, pour espérer pouvoir réactiver l'application de temps en temps.
Je les ai faites:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
return true
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
debugPrint("performFetchWithCompletionHandler")
getData()
completionHandler(UIBackgroundFetchResult.newData)
}
func getData(){
debugPrint("getData")
}
J'ai également déjà activé les capacités de récupération en arrière-plan. C'est tout ce que j'ai fait. Et puis je lance l'application. la fonction n'a jamais été appelée même après une heure (le téléphone dormait).
Quelles autres choses dois-je faire pour que la fonction soit appelée?
Vous avez effectué un grand nombre des étapes nécessaires:
Activé "fond chercher" l'onglet "Capacités" de votre projet;
Implémenté application(_:performFetchWithCompletionHandler:)
;
Appelé setMinimumBackgroundFetchInterval(_:)
dans application(_:didFinishLaunchingWithOptions:)
.
Cela dit, quelques observations:
Je vérifierais les autorisations de l'application dans "Paramètres" "" Général "" "Actualiser l'application en arrière-plan". Cela garantit que non seulement vous avez réussi à demander la récupération en arrière-plan dans votre plist, mais qu'il est activé en général, ainsi que pour votre application en particulier.
Assurez-vous de ne pas tuer l'application (c'est-à-dire en appuyant deux fois sur le bouton d'accueil et en balayant votre application pour forcer l'application à se terminer). Si l'application est supprimée, cela empêchera la récupération 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 appareil physique, ne pas l'exécuter à partir de Xcode. Vous devez utiliser un système de journalisation qui vous montre l'activité même lorsque vous n'exécutez pas l'application via Xcode.
J'utilise os_log
et regardez-le à partir de la console (voir WWDC 2016 Journalisation unifiée et suivi d'activité ) ou utilisez publier une notification via Framework UserNotifications (voir WWDC 2016 Introduction aux notifications ) donc je suis averti lorsque l'application fait quelque chose de remarquable en arrière-plan. Ou j'ai créé mes propres systèmes de journalisation externes (par exemple, écrire 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 cela sans l'exécuter indépendamment de Xcode. Tout comportement lié à l'arrière-plan change lors de l'exécution d'une application connectée au débogueur.
Comme PGDev a dit , vous n'avez aucun contrôle sur le moment où la récupération en arrière-plan a lieu. Il prend en compte de nombreux facteurs mal documentés (connectivité wifi, connectée à l'alimentation, fréquence d'utilisation des applications de l'utilisateur, quand d'autres applications peuvent tourner, etc.).
Cela dit, lorsque j'ai activé la récupération en arrière-plan, exécuté l'application depuis l'appareil (pas Xcode) et l'avoir connectée au wifi et à l'alimentation, la première récupération en arrière-plan appelée est apparue sur mon iPhone 7+ dans les 10 minutes suivant la suspension de 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 réellement la requête URLSession
à un moment donné de son cours normal lorsque vous l'exécutez (c'est-à-dire lorsque vous exécutez l'application normalement, pas via la récupération en arrière-plan). Si vous disposez d'une application de test qui n'émet aucune demande, elle ne semble pas activer la fonction de récupération en arrière-plan. (Ou à tout le moins, cela affecte gravement la fréquence des demandes de récupération en arrière-plan.)
Il semblerait que le système d'exploitation arrête d'émettre des appels de récupération d'arrière-plan ultérieurs vers votre application si les appels de récupération d'arrière-plan précédents n'ont pas réellement abouti à l'émission d'une demande réseau. (Cela peut être une permutation du point précédent; ce n'est pas tout à fait clair.) Je soupçonne Apple essaie d'empêcher les développeurs d'utiliser le mécanisme de récupération en arrière-plan pour des tâches qui ne récupèrent vraiment rien.
Notez que votre application n'a pas beaucoup de temps pour effectuer la demande, donc si vous émettez une demande, vous voudrez peut-être vous renseigner uniquement si des données sont disponibles, mais ne pas essayer de télécharger toutes les données elles-mêmes. Vous pouvez ensuite lancer une session d'arrière-plan pour démarrer les téléchargements longs. De toute évidence, 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 demande en appelant la fin d'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 en arrière-plan suivantes sont tentées.
Si l'application ne traite pas les demandes en arrière-plan, je pourrais suggérer de supprimer l'application de l'appareil et de la réinstaller. J'ai eu une situation où, lors du test de la récupération en arrière-plan, où les demandes ont cessé de fonctionner (peut-être à la suite d'une demande de récupération en arrière-plan échouée lors du test d'une précédente itération de l'application). Je trouve que le supprimer et le réinstaller est un bon moyen de réinitialiser le processus de récupération en arrière-plan.
À titre d'illustration, voici un exemple qui effectue des récupérations d'arrière-plan avec succès. J'ai également ajouté le framework UserNotifications et os_log
appelle pour fournir un moyen de surveiller la progression lorsqu'il n'est pas connecté à Xcode (c'est-à-dire 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 pour les 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()
}
}
La récupération en arrière-plan est automatiquement lancée par le système à intervalles appropriés.
Une fonctionnalité très importante et intéressante de la récupération en arrière-plan est sa capacité à apprendre les heures qui devraient permettre de lancer une application en arrière-plan et de la mettre à jour. Supposons par exemple qu'un utilisateur utilise une application d'actualités tous les matins vers 8h30 (lisez des actualités avec du café chaud). Après quelques utilisations, le système apprend qu'il est tout à fait possible que la prochaine fois que l'application s'exécute soit à peu près au même moment, il prend donc soin de la laisser en ligne et d'être mise à jour avant l'heure de lancement habituelle (cela pourrait être vers 8h00). De cette façon, lorsque l'utilisateur ouvre l'application, le contenu nouveau et actualisé l'attend, et non l'inverse! Cette fonctionnalité est appelée prédiction d'utilisation .
Pour tester si le code que vous avez écrit fonctionne correctement ou non, vous pouvez vous référer au tutoriel de Raywenderlich sur Récupération en arrière-plan .
Tutoriel : https://www.raywenderlich.com/143128/background-modes-tutorial-getting-started (Rechercher: Tester la récupération en arrière-plan)