Sincronización de datos JSON remotos con almacenamiento en caché local en iOS Swift


Estoy tratando de encontrar la solución para el procesamiento simple de todos los pasos necesarios para el consumo de solo lectura de datos JSON remotos en dispositivos iOS. Significa obtener datos JSON remotos, almacenar en caché local en el dispositivo iOS para su uso sin conexión, actualizar la caché, analizar datos JSON. Creo que es un requisito muy común para todas las aplicaciones móviles hoy en día.

Sé que es posible descargar manualmente el archivo JSON remoto, almacenarlo en una base de datos local o un archivo en un dispositivo iOS y cuando la red no esté disponible, recuperarlo desde el almacenamiento local de lo contrario descargarlo de la red. Ahora lo hago manualmente. :) Pero son muchos los pasos que hope es posible hacer usando frameworks / libraries, ¿no es así?

Así que probé HanekeSwift framework que hace casi todo lo que necesito, pero solo almacena en caché JSON remoto (e Imágenes), pero no actualiza la caché!! lo cual no es útil para mí. Sé también que existe Alamofire y SwiftyJSON pero no tengo ninguna experiencia con ellos.

¿Tienes experiencia en cómo hacerlo ¿eso?

Resumen

  • bibliotecas o frameworks para soporte iOS8 en Swift
  • descargar JSON remoto y almacenar en caché local
  • posibilidad de actualizar la caché local desde su origen
  • buen bono es fácil JSON parsing

introduzca la descripción de la imagen aquí

¡Gracias! Michal

Author: kolisko, 2015-02-09

3 answers

¡Gran pregunta!

Puedes lograr esto con una combinación de Alamofire y SwiftyJSON. Lo que recomendaría es una combinación de varias cosas para hacer esto lo más fácil posible.

Creo que tienes dos enfoques para obtener el JSON.

  1. Obtenga los datos JSON en memoria y use una política de caché
  2. Descargue los datos JSON al disco directamente en su caché local

Opción 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
       }

Opción 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
}

La opción 1 ciertamente aprovecha la mayor cantidad de almacenamiento en caché compatible con Apple. Creo que con lo que está tratando de hacer, debería ser capaz de aprovechar la NSURLSessionConfiguration y una política de caché en particular para lograr lo que está buscando hacer.

La opción 2 requerirá una cantidad de trabajo mucho mayor, y será un sistema un poco extraño si aprovecha una política de caché que realmente almacena en caché los datos en el disco. Las descargas terminarían copiando ya datos almacenados en caché. Así es como sería el flujo si tu solicitud existiera en tu caché de url personalizada.

  1. Hacer solicitud de descarga
  2. La solicitud se almacena en caché para que los datos almacenados en caché se carguen en NSInputStream
  3. Los datos se escriben en el URL proporcionado a través de NSOutputStream
  4. El serializador de respuesta se llama donde se cargan los datos de nuevo en la memoria
  5. Los datos se analizan usando SwiftyJSON en objetos modelo

Esto es bastante derrochador a menos que sea descarga de archivos muy grandes. Podría tener problemas de memoria si carga todos los datos de la solicitud en la memoria.

La copia de los datos almacenados en caché a la URL se implementará muy probablemente a través de NSInputStream y NSOutputStream. Todo esto es manejado internamente por Apple por el framework Foundation. Esta debe ser una forma muy eficiente de memoria para mover los datos. El inconveniente es que necesita copiar todo el conjunto de datos antes de poder acceder se.

NSURLCache

Otra cosa que puede ser muy útil aquí para usted es la capacidad de obtener una respuesta en caché directamente desde su NSURLCache. Echa un vistazo al método cachedReponseForRequest: que se puede encontrar aquí.

SwiftyJSON

El último paso es analizar los datos JSON en objetos de modelo. SwiftyJSON hace esto muy fácil. Si está utilizando La opción 1 desde arriba, entonces podría usar el serializador de respuesta personalizada en el Alamofire-SwiftyJSON . Eso se vería algo así como lo siguiente:

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

Ahora, si usaste La opción 2 , necesitarás cargar los datos desde el disco, luego inicializar un objeto SwiftyJSON y comenzar a analizar que se vería algo como esto:

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

Eso debería cubrir todas las herramientas que necesitas para lograr lo que intentas. Cómo diseñar la solución exacta ciertamente depende de usted, ya que hay muchas formas posibles de hacerlo.

 39
Author: cnoon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-12 06:14:01

A continuación se muestra el código que utilicé para almacenar en caché mis solicitudes utilizando Alamofire y SwiftyJSON - Espero que ayude a alguien por ahí

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
Author: Charl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-10 04:43:44

Esta es una versión Swift 3 basada en la respuesta de Charl (usando SwiftyJSON y 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
Author: lenooh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-07 23:56:34