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

62

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

我知道可以手动下载远程JSON文件,将其存储到iOS设备的本地数据库或文件中,并在网络不可用时从本地存储中提取它,否则从网络上下载。我现在手动完成这个过程。 :) 但这是很多步骤,希望能够使用框架/库来完成,是吗?

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

您有任何经验如何做到这一点吗?

摘要

  • iOS8支持的Swift库或框架
  • 下载远程JSON并存储到本地缓存
  • 可以从其来源刷新本地缓存的可能性
  • 额外加分是易于JSON解析

enter image description here

3个回答

39

好问题!

你可以使用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肯定利用了苹果支持的最大缓存量。我认为根据你的需求,你应该能够利用NSURLSessionConfiguration和特定的缓存策略来实现你想要做的事情。

选项2需要更大量的工作,并且如果你使用实际上在磁盘上缓存数据的缓存策略,那么它将是一个有点奇怪的系统。下载将复制已经缓存的数据。 如果您的请求存在于自定义URL缓存中,则流程将如下所示:

  1. 发起下载请求
  2. 请求已缓存,因此将缓存的数据加载到NSInputStream中
  3. 数据通过NSOutputStream写入提供的URL
  4. 调用响应序列化器,在其中将数据加载回内存
  5. 然后使用SwiftyJSON解析数据为模型对象

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

将缓存的数据复制到提供的URL最有可能通过NSInputStream和NSOutputStream实现。这一切都由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到底做了什么?ad1-如果可用,则从缓存中加载数据,ad2-刷新缓存中的数据?也许这是个愚蠢的问题,但是这样理解是否正确? - kolisko
如果没有网络连接,那么您将在response中收到网络错误。您可以添加逻辑,仅在有互联网连接时尝试下载数据。至于您的其他问题,我已经对上面关于NSURLCache和不同的NSURLRequestCachePolicy做了一些重大更新。希望这能为您提供更好的方向,以利用已经存在的苹果缓存系统。 - cnoon
2
在我的应用程序中,我会使用选项1并使用NSURLCache。当没有网络连接时,我想使用缓存响应。是否有一种方法可以直接使用缓存而不进行请求,在没有网络连接的情况下?谢谢。 - giograno
如果我同时使用这两个东西会发生什么? - Sivajee Battina
@cnoon,您能否解释一下现金数据的到期时间是多少?并且在服务调用期间它会自动更新吗? - Nikunj Kumbhani
抱歉打扰你了,Christian。你能否看一下这个问题:使用storeCachedResponse存储后未检索到URLResponse - mfaani

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

能否修改缓存数据以后更新服务器上的数据? - fullmoon

2

这是基于Charl的答案(使用SwiftyJSONAlamofire)的Swift 3版本:

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

    }
}

通过调用动态请求(不同的请求URL和参数),我得到了相同的响应,该响应在第一次被存储在缓存中。有什么想法吗? - jaya raj
@lenooh。在Swift 3中,使用Alamofire是否可以离线存储数据? - Uma Madhavi
@jayaraj和lenooh..这是我的代码,https://ibb.co/nKKXkk。我如何在Swift 3中实现离线存储? - Uma Madhavi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接