web-dev-qa-db-fra.com

Récupération en arrière-plan iOS10

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?

12
user6539552

Vous avez effectué un grand nombre des étapes nécessaires:

Cela dit, quelques observations:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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()
    }

}
13
Rob

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)

5
PGDev