web-dev-qa-db-fra.com

Synchronisation des données JSON distantes avec le stockage de cache local dans iOS Swift

J'essaie de trouver la solution pour un traitement simple de toutes les étapes nécessaires pour la consommation en lecture seule des données JSON distantes sur les appareils iOS. Cela signifie récupérer des données JSON distantes, stocker dans le cache local sur un appareil iOS pour une utilisation hors ligne, actualiser le cache, analyser les données JSON. Je pense que c'est une exigence très courante pour toutes les applications mobiles de nos jours.

Je sais qu'il est possible de télécharger manuellement un fichier JSON distant, de le stocker sur une base de données locale ou un fichier sur un appareil iOS et lorsque le réseau n'est pas disponible, récupérez-le dans le stockage local, sinon téléchargez-le sur Internet. Je le fais manuellement maintenant. :) Mais il y a beaucoup d'étapes qu'il est possible de faire en utilisant des frameworks/bibliothèques, n'est-ce pas?

J'ai donc essayé le framework HanekeSwift qui fait presque tout ce dont j'ai besoin mais il ne fait que mettre en cache le JSON distant (et les images) mais ne rafraîchit pas le cache !! ce qui ne m'est pas utile. Je sais aussi qu'il existe Alamofire et SwiftyJSON mais je n'ai aucune expérience avec eux.

Avez-vous une expérience comment faire cela?

Résumé

  • bibliothèques ou frameworks pour la prise en charge d'iOS8 dans Swift
  • télécharger JSON distant et stocker dans le cache local
  • possibilité de rafraîchir le cache local depuis son origine
  • Un bon bonus est une analyse JSON facile

enter image description here

60
kolisko

Grande question!

Vous pouvez absolument accomplir cela avec une combinaison d'Alamofire et de SwiftyJSON. Ce que je recommanderais, c'est une combinaison de plusieurs choses pour rendre cela aussi simple que possible.

Je pense que vous avez deux approches pour récupérer le JSON.

  1. Récupérer les données JSON en mémoire et utiliser une politique de cache
  2. Téléchargez les données JSON sur le disque directement dans votre cache local

Option 1

// Create a shared URL cache
let memoryCapacity = 500 * 1024 * 1024; // 500 MB
let diskCapacity = 500 * 1024 * 1024; // 500 MB
let cache = NSURLCache(memoryCapacity: memoryCapacity, diskCapacity: diskCapacity, diskPath: "shared_cache")

// Create a custom configuration
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders
configuration.HTTPAdditionalHeaders = defaultHeaders
configuration.requestCachePolicy = .UseProtocolCachePolicy // this is the default
configuration.URLCache = cache

// Create your own manager instance that uses your custom configuration
let manager = Alamofire.Manager(configuration: configuration)

// Make your request with your custom manager that is caching your requests by default
manager.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"], encoding: .URL)
       .response { (request, response, data, error) in
           println(request)
           println(response)
           println(error)

           // Now parse the data using SwiftyJSON
           // This will come from your custom cache if it is not expired,
           // and from the network if it is expired
       }

Option 2

let URL = NSURL(string: "/whereever/your/local/cache/lives")!

let downloadRequest = Alamofire.download(.GET, "http://httpbin.org/get") { (_, _) -> NSURL in
    return URL
}

downloadRequest.response { request, response, _, error in
    // Read data into memory from local cache file now in URL
}

L'option 1 exploite certainement la plus grande quantité de mise en cache prise en charge Apple. Je pense qu'avec ce que vous essayez de faire, vous devriez pouvoir utiliser le NSURLSessionConfiguration et un cache policy particulier pour accomplir ce que vous cherchez à faire.

L'option 2 nécessitera un travail beaucoup plus important et sera un peu un système étrange si vous exploitez une politique de cache qui met en cache les données sur le disque . Les téléchargements finiraient par copier des données déjà mises en cache. Voici à quoi ressemblerait le flux si votre demande existait dans votre cache d'URL personnalisé.

  1. Faire une demande de téléchargement
  2. La demande est mise en cache afin que les données mises en cache soient chargées dans NSInputStream
  3. Les données sont écrites dans le URL fourni via NSOutputStream
  4. Le sérialiseur de réponse est appelé lorsque vous chargez les données en mémoire
  5. Les données sont ensuite analysées à l'aide de SwiftyJSON dans des objets de modèle

C'est assez inutile à moins que vous ne téléchargiez de très gros fichiers. Vous pouvez potentiellement rencontrer des problèmes de mémoire si vous chargez toutes les données de demande en mémoire.

La copie des données mises en cache dans le URL fourni sera très probablement implémentée via NSInputStream et NSOutputStream. Tout cela est géré en interne par Apple par le framework Foundation. Cela devrait être un moyen très efficace en termes de mémoire pour déplacer les données. L'inconvénient est que vous devez copier l'ensemble de données avant de pouvoir y accéder. .

NSURLCache

Une autre chose qui peut être très utile ici est la possibilité de récupérer une réponse en cache directement à partir de votre NSURLCache. Jetez un œil au cachedReponseForRequest: méthode qui peut être trouvée ici .

SwiftyJSON

La dernière étape consiste à analyser les données JSON en objets de modèle. SwiftyJSON rend cela très facile. Si vous utilisez l'option 1 ci-dessus, vous pouvez utiliser le sérialiseur de réponse personnalisé dans Alamofire-SwiftyJSON . Cela ressemblerait à quelque chose comme ceci:

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
         .responseSwiftyJSON { (request, response, json, error) in
             println(json)
             println(error)
         }

Maintenant, si vous avez utilisé l'option 2 , vous devrez charger les données à partir du disque, puis initialiser un objet SwiftyJSON et commencer à analyser ce qui ressemblerait à quelque chose comme ceci :

let data = NSData(contentsOfFile: URL.path)!
let json = JSON(data: data)

Cela devrait couvrir tous les outils en ce que vous devriez avoir besoin d'accomplir ce que vous essayez. La façon dont vous concevez la solution exacte dépend certainement de vous, car il existe de nombreuses façons de le faire.

39
cnoon

Vous trouverez ci-dessous le code que j'ai utilisé pour mettre en cache mes demandes à l'aide d'Alamofire et de SwiftyJSON - j'espère que cela aide quelqu'un

func getPlaces(){
    //Request with caching policy
    let request = NSMutableURLRequest(URL: NSURL(string: baseUrl + "/places")!, cachePolicy: .ReturnCacheDataElseLoad, timeoutInterval: 20)
    Alamofire.request(request)
        .responseJSON { (response) in
            let cachedURLResponse = NSCachedURLResponse(response: response.response!, data: (response.data! as NSData), userInfo: nil, storagePolicy: .Allowed)
            NSURLCache.sharedURLCache().storeCachedResponse(cachedURLResponse, forRequest: response.request!)

            guard response.result.error == nil else {
                // got an error in getting the data, need to handle it
                print("error calling GET on /places")
                print(response.result.error!)
                return
            }

            let swiftyJsonVar = JSON(data: cachedURLResponse.data)
            if let resData = swiftyJsonVar["places"].arrayObject  {
                // handle the results as JSON, without a bunch of nested if loops
                self.places = resData

                //print(self.places)

            }
            if self.places.count > 0 {
                self.tableView.reloadData()
            }
    }
}
3
Chazo

Il s'agit d'une version Swift 3 basée sur la réponse de Charl (en utilisant SwiftyJSON et Alamofire ):

func getData(){

    let query_url = "http://YOUR-URL-HERE"   

    // escape your URL
    let urlAddressEscaped = query_url.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)


    //Request with caching policy
    let request = URLRequest(url: URL(string: urlAddressEscaped!)!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 20)

    Alamofire.request(request)
        .responseJSON { (response) in
            let cachedURLResponse = CachedURLResponse(response: response.response!, data: (response.data! as NSData) as Data, userInfo: nil, storagePolicy: .allowed)
            URLCache.shared.storeCachedResponse(cachedURLResponse, for: response.request!)

            guard response.result.error == nil else {

                // got an error in getting the data, need to handle it
                print("error fetching data from url")
                print(response.result.error!)
                return

            }

            let json = JSON(data: cachedURLResponse.data) // SwiftyJSON

            print(json) // Test if it works

            // do whatever you want with your data here

    }
}
1
lenooh