2017-06-01 2 views
3

J'ai essayé d'implémenter l'extraction d'arrière-plan pour, espérons-le, réveiller l'application de temps en temps.iOS10 Recherche d'arrière-plan

Je l'ai fait:

 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") 
    } 

Je permettra également chercher des capacités déjà arrière-plan. C'est tout ce que j'ai fait. Et puis je lance l'application. la fonction n'a jamais appelé même après une heure (le téléphone dormait).

Quelles sont les autres choses que je dois faire pour que la fonction soit appelée?

+0

Oui, activé Extraction en arrière-plan; Paramétrage également effectué, autorisation d'autorisation, l'application est à l'arrière-plan – user6539552

Répondre

0

L'extraction d'arrière-plan est lancée automatiquement par le système à des intervalles appropriés.

Une caractéristique très importante et fraîche de l'arrière-plan Fetch est sa capacité d'apprendre les temps qui devraient permettre à une application d'être lancée pour l'arrière-plan et sont mis à jour. Supposons par exemple qu'un utilisateur utilise une application de nouvelles chaque matin vers 8h30 (lire quelques nouvelles avec du café chaud). Après quelques utilisations, le système apprend que il est tout à fait possible que la prochaine fois que l'application tournera autour de en même temps, il faudra donc le mettre en ligne et se mettre à jour avant le lancement habituel (ça pourrait être vers 8h00). De cette façon, lorsque l'utilisateur ouvre l'application le contenu nouveau et rafraîchi est là en attente pour lui, et non le contraire! Cette fonctionnalité est appelée utilisation prédiction.

Pour vérifier si le code que vous avez écrit fonctionne correctement ou non, vous pouvez consulter le tutoriel de Raywenderlich sur Contexte Fetch.

Tutoriel: https://www.raywenderlich.com/143128/background-modes-tutorial-getting-started (Rechercher: Test Contexte Fetch)

4

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

Cela dit, quelques observations:

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

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

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

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

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

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

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

}