Синхронизация удаленных данных JSON с локальным хранилищем кешей в iOS Swift

Я пытаюсь найти решение для простой обработки всех необходимых шагов для использования только для чтения данных удаленного JSON на устройствах iOS. Это означает получение удаленных данных JSON, сохранение локального кеша на устройстве iOS для автономного использования, обновление кеша, анализ данных JSON. Я думаю, что это очень распространенное требование для всех мобильных приложений в наши дни.

Я знаю, что можно вручную загрузить удаленный JSON файл, сохранить его в локальном db или файле на устройстве iOS, а когда сеть недоступна, извлеките его из локального хранилища, иначе выгрузите его из сети. Сейчас я делаю это вручную.:) Но много шагов, на которые можно надеяться, используя фреймворки/библиотеки, не?

Итак, я попробовал фреймворк HanekeSwift, который делает почти все, что мне нужно, но он только кэширует удаленные JSON (и изображения), но не обновляет кеш! что не полезно для меня. Я также знаю, что существует Alamofire и SwiftyJSON, но у меня нет опыта с ними.

Есть ли у вас какой-либо опыт, как это сделать?

Резюме

  • библиотеки или фреймворки для поддержки iOS8 в Swift
  • загрузить удаленный JSON и сохранить в локальный кеш
  • возможность обновления локального кэша от него.
  • приятный бонус легко JSON-синтаксический анализ

enter image description here

Спасибо! Михал

Ответ 1

Отличный вопрос!

Вы можете абсолютно выполнить это с помощью комбинации Alamofire и SwiftyJSON. Я бы порекомендовал это сочетание нескольких вещей, чтобы сделать это как можно проще.

Я думаю, у вас есть два подхода к извлечению JSON.

  • Получить данные JSON в памяти и использовать политику кэширования
  • Загрузите данные JSON на диск непосредственно в локальный кеш

Вариант 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
       }

Вариант 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
}

Вариант 1, безусловно, использует наибольшее количество поддерживаемых Apple кеширования. Я думаю, что с помощью того, что вы пытаетесь сделать, вы сможете использовать NSURLSessionConfiguration и конкретную политику кэша , чтобы выполнить то, что вы хотите сделать.

Вариант 2 потребует гораздо большего объема работы и будет немного странной системой, если вы используете политику кэширования, которая фактически кэширует данные на диске. Загрузки закончили копирование уже кэшированных данных. Здесь будет поток, как если бы ваш запрос существовал в вашем пользовательском кэше URL.

  • Сделать запрос на скачивание
  • Запрос кэшируется, поэтому кэшированные данные загружаются в NSInputStream
  • Данные записываются в предоставленный URL через NSOutputStream
  • Серийный запрос ответа вызывается там, где вы загружаете данные в память
  • Затем данные анализируются с использованием SwiftyJSON в объектах модели.

Это довольно расточительно, если вы не загружаете очень большие файлы. При загрузке всех данных запроса в память вы можете столкнуться с проблемами памяти.

Копирование кэшированных данных в предоставленный URL, скорее всего, будет реализовано через NSInputStream и NSOutputStream. Все это обрабатывается Apple со стороны Foundation. Это должно быть очень эффективным для хранения данных. Недостатком является то, что вам нужно скопировать весь набор данных, прежде чем вы сможете получить к нему доступ.

NSURLCache

Еще одна вещь, которая может быть очень полезной для вас, - это возможность получить кешированный ответ прямо из вашего NSURLCache. Взгляните на метод cachedReponseForRequest:, который можно найти здесь.

SwiftyJSON

Последний шаг - синтаксический анализ данных JSON в объектах модели. SwiftyJSON делает это очень легко. Если вы используете Вариант 1 сверху, то вы можете использовать настраиваемый сериализатор ответов в Alamofire-SwiftyJSON. Это выглядит примерно так:

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

Теперь, если вы использовали Вариант 2, вам нужно будет загрузить данные с диска, а затем инициализировать объект SwiftyJSON и начать синтаксический анализ, который будет выглядеть примерно так:

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

Это должно охватывать все инструменты, в которых вам необходимо выполнить то, что вы пытаетесь сделать. Как вы строите точное решение, безусловно, зависит от вас, поскольку существует так много возможных способов сделать это.

Ответ 2

Ниже приведен код, который я использовал для кэширования своих запросов с использованием Alamofire и SwiftyJSON. Надеюсь, что это поможет кому-то там.

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

Это версия Swift 3 на основе ответа Charl (используя SwiftyJSON и 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

    }
}