web-dev-qa-db-ja.com

iOSのリモートJSONデータをローカルキャッシュストレージに同期するSwift

IOSデバイスで読み取り専用のリモートJSONデータを使用するために必要なすべてのステップを簡単に処理するためのソリューションを見つけようとしています。リモートJSONデータの取得、オフライン使用のためのiOSデバイス上のローカルキャッシュへの保存、キャッシュの更新、JSONデータの解析を意味します。最近のすべてのモバイルアプリに共通の要件だと思います。

リモートのJSONファイルを手動でダウンロードし、iOSデバイスのローカルdbまたはファイルに保存し、ネットワークが利用できない場合はローカルストレージから取得するか、そうでなければネットからダウンロードすることが可能であることを知っています。私は今それを手動で行います。 :)しかし、フレームワーク/ライブラリを使用して行うことができる希望はたくさんありますよね?

だから、私は必要なことをほぼすべて行うHanekeSwiftフレームワークを試しましたが、リモートJSON(およびイメージ)のキャッシュのみを行い、キャッシュを更新しません!!私には役に立たない。 AlamofireとSwiftyJSONが存在することも知っていますが、それらの経験はありません。

それを行う方法はありますか?

概要

  • swiftのiOS8サポート用のライブラリまたはフレームワーク
  • リモートJSONをダウンロードしてローカルキャッシュに保存する
  • originからローカルキャッシュを更新する可能性
  • 素敵なボーナスは簡単なJSON解析です

enter image description here

60
kolisko

いい質問です!

AlamofireとSwiftyJSONを組み合わせることで、これを確実に実現できます。私がお勧めするのは、これをできるだけ簡単にするためのいくつかのことの組み合わせです。

JSONを取得するには2つの方法があると思います。

  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と特定の キャッシュポリシー を活用して、目的の処理を実行できる必要があります。

オプション2は非常に多くの作業を必要とし、実際にデータをディスクにキャッシュするキャッシュポリシーを活用する場合、少し奇妙なシステムになります。ダウンロードは、既にキャッシュされたデータをコピーすることになります。リクエストがカスタムURLキャッシュに存在する場合のフローは次のとおりです。

  1. ダウンロードリクエストをする
  2. 要求はキャッシュされるため、キャッシュされたデータはNSInputStreamにロードされます
  3. NSOutputStreamを介して、提供されたURLにデータが書き込まれます
  4. 応答シリアライザーは、データをメモリに戻す場所で呼び出されます
  5. その後、データはSwiftyJSONを使用してモデルオブジェクトに解析されます

非常に大きなファイルをダウンロードしない限り、これは非常に無駄です。すべてのリクエストデータをメモリにロードすると、メモリの問題が発生する可能性があります。

キャッシュされたデータを提供されたURLにコピーすることは、おそらくNSInputStreamとNSOutputStreamを介して実装されます。これは、FoundationフレームワークによってAppleによって内部的にすべて処理されます。これは、データを移動するための非常にメモリ効率の良い方法です。欠点は、アクセスする前にデータセット全体をコピーする必要があることです。

NSURLCache

ここで非常に役立つもう1つのことは、キャッシュされた応答をNSURLCacheから直接フェッチする機能です。 here にある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)

それはあなたがしようとしていることを達成するために必要なすべてのツールをカバーするはずです。正確なソリューションをどのように設計するかは、可能な限り多くの方法があるため、あなた次第です。

39
cnoon

以下は、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
Chazo

これは、Charlの回答に基づくSwift 3バージョンです( 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

    }
}
1
lenooh