正在将远程 JSON 数据同步到 iOS Swift 中的本地缓存存储

Synchronizing remote JSON data to local cache storage in iOS Swift

我正在尝试找到简单处理所有必要步骤的解决方案,以便在 iOS 设备上以只读方式使用远程 JSON 数据。这意味着获取远程 JSON 数据,存储到 iOS 设备上的本地缓存以供离线使用,刷新缓存,解析 JSON 数据。我认为这是当今所有移动应用程序非常普遍的要求。

我知道可以手动下载远程 JSON 文件,将其存储到本地数据库或 iOS 设备上的文件,当网络不可用时从本地存储获取它,否则从网。我现在手动做。 :) 但是有很多步骤希望可以通过使用 frameworks/libraries 来完成,不是吗?

所以我尝试了 HanekeSwift 框架,它几乎可以满足我的所有需求,但它只缓存远程 JSON(和图像),但不刷新缓存!这对我没用。我也知道存在 Alamofire 和 SwiftyJSON,但我对它们没有任何经验。

你有经验吗?

总结

好问题!

结合使用 Alamofire 和 SwiftyJSON,您完全可以做到这一点。我建议的是将几件事结合起来,使这尽可能简单。

我认为你有两种获取 JSON 的方法。

  1. 获取内存中的 JSON 数据并使用缓存策略
  2. 将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 和特定的 cache policy 来完成你想要做的事情。

选项 2 将需要更多的工作,并且如果您利用实际上将数据缓存在磁盘上的缓存策略,将会是一个有点奇怪的系统。下载最终会复制已经缓存的数据。如果您的请求存在于您的自定义 url 缓存中,流程将是这样的。

  1. 发出下载请求
  2. 请求被缓存,所以缓存的数据加载到 NSInputStream
  3. 数据通过NSOutputStream
  4. 写入提供的URL
  5. 在将数据加载回内存的位置调用响应序列化程序
  6. 然后使用 SwiftyJSON 将数据解析为模型对象

除非您下载非常大的文件,否则这是相当浪费的。如果将所有请求数据加载到内存中,可能 运行 会出现内存问题。

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

另一件可能对您非常有用的事情是能够直接从您的 NSURLCache 获取缓存的响应。看看 cachedReponseForRequest: 方法可以找到 here.

斯威夫特JSON

最后一步是将 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)

这应该涵盖了您完成尝试所需的所有工具。如何构建确切的解决方案当然取决于您,因为有很多可能的方法。

下面是我使用 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()
            }
    }
}

这是一个 Swift 3 版本,基于 Charl 的回答(使用 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

    }
}