Synchronizing remote JSON data to local cache storage in iOS Swift Synchronizing remote JSON data to local cache storage in iOS Swift ios ios

Synchronizing remote JSON data to local cache storage in iOS Swift


Great question!

You can absolutely accomplish this with a combination of Alamofire and SwiftyJSON. What I would recommend is a combination of several things to make this as easy as possible.

I think you have two approaches to fetching the JSON.

  1. Fetch the JSON data in-memory and use a cache policy
  2. Download the JSON data to disk directly to your local cache

Option 1

// Create a shared URL cachelet memoryCapacity = 500 * 1024 * 1024; // 500 MBlet diskCapacity = 500 * 1024 * 1024; // 500 MBlet cache = NSURLCache(memoryCapacity: memoryCapacity, diskCapacity: diskCapacity, diskPath: "shared_cache")// Create a custom configurationlet configuration = NSURLSessionConfiguration.defaultSessionConfiguration()var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeadersconfiguration.HTTPAdditionalHeaders = defaultHeadersconfiguration.requestCachePolicy = .UseProtocolCachePolicy // this is the defaultconfiguration.URLCache = cache// Create your own manager instance that uses your custom configurationlet manager = Alamofire.Manager(configuration: configuration)// Make your request with your custom manager that is caching your requests by defaultmanager.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}

Option 1 certainly leverages the largest amount of Apple supported caching. I think with what you're trying to do, you should be able to leverage the NSURLSessionConfiguration and a particular cache policy to accomplish what you're looking to do.

Option 2 will require a much larger amount of work, and will be a bit of a strange system if you leverage a cache policy that actually caches data on disk. Downloads would end up copying already cached data. Here's what the flow would be like if your request existed in your custom url cache.

  1. Make download request
  2. Request is cached so cached data loaded into NSInputStream
  3. Data is written to the provided URL through NSOutputStream
  4. Response serializer is called where you load the data back into memory
  5. Data is then parsed using SwiftyJSON into model objects

This is quite wasteful unless you are downloading very large files. You could potentially run into memory issues if you load all the request data into memory.

Copying the cached data to the provided URL will most likely be implemented through NSInputStream and NSOutputStream. This is all handled internally by Apple by the Foundation framework. This should be a very memory efficient way to move the data. The downside is that you need to copy the entire dataset before you can access it.

NSURLCache

One other thing that may be very useful here for you is the ability to fetch a cached response directly from your NSURLCache. Take a look at the cachedReponseForRequest: method which can be found here.

SwiftyJSON

The last step is parsing the JSON data into model objects. SwiftyJSON makes this very easy. If you're using Option 1 from above, then you could use the custom response serializer in the Alamofire-SwiftyJSON. That would look something like the following:

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

Now if you used Option 2, you'll need to load the data from disk, then initialize a SwiftyJSON object and begin parsing which would look something like this:

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

That should cover all the tools in that you should need to accomplish what you attempting. How you architect the exact solution is certainly up to you since there are so many possible ways to do it.


Below is the code i used to cache my requests using Alamofire and SwiftyJSON - I hope it helps someone out there

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


This is a Swift 3 version based on Charl's answer (using SwiftyJSON and 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    }}