34
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Webサーバのキャッシュ設定とiOS URLSessionのキャッシュの動作

Last updated at Posted at 2019-11-13

はじめに

サーバ間からダウンロードしたデータをスマホに表示させたいと思い、キャッシュを使った効率的な方法を検索しました。しかし、いろいろな方法が書いてあり、古い情報もありそうで、どれが正しいかわからなかったので、実機で検証してみました。結論としては、サーバを正しく設定すれば、基本的には、iOSはデフォルトのURLSessionを利用できました。例外として、HTTP応答がbyte-range requestsの場合は、キャッシュを使わないようなので、このような場合には別の方法を見つける必要がありそうです。
 今回、一番つまずいたところは、ネットワーク的には正しくキャッシュの動作(Status:304)をしているのに、アプリのログを見ると、キャッシュを使っていないように見える(Status:200)ところでした。同じように困る方もいると思うので、検証結果も最後に載せておきます。

やりたいこと

  • サーバのデータをスマホにダウンロードして表示
  • できるだけ最新のデータを利用する
  • 同一のデータの場合は、ダウンロードせずに、スマホ内のキャッシュを使う
  • 実装はできるだけ標準のものを使い、自分で実装する部分を減らす。

iOSでのキャッシュポリシーの実装

SWIFTでよく使うURLSessionのキャッシュポリシーは、設定しない場合のデフォルトポリシーは、useProtocolCachePolicyです。この場合の動作は、[Apple社の資料]
(
https://developer.apple.com/documentation/foundation/nsurlrequest/cachepolicy/useprotocolcachepolicy)に書かれています。以下は、この図の引用です。今回実現したいことは、この図に書いた1〜3を実現することなので、URLSessionをそのまま使えば実現できそうです。
しかし、例外として、byte-range requestsの場合には、キャッシュポリシーは、reloadIgnoringLocalCacheDataになるようです。

cachepolicy

URLSessionを利用すると簡単なコードでサーバからHTTP GETできます。以下は、検証に使ったコードです。

検証コード.swift
func getHttp() {
  let urlString = "http://example.com/"
  guard let url = URL(string: urlString) else { return }
  let request = URLRequest(url: url)

  let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
    debugPrint(response.debugDescription) // HTTPヘッダの表示
  }
  task.resume()
}

Webサーバの設定

上記のようにスマホ側にコンテンツをキャッシュさせるためには、Webサーバに設定が必要です。キャッシュが新しいかどうかを確認するためのHTTPヘッダは、RFC7232で2つ定義されています。Last-ModifiedETag。Webサーバから少なくともどちらか1つのHTTPヘッダで応答する必要があります。

また、キャッシュが最新かを常に検証(validation)するためには、WEBサーバで以下のHTTPヘッダのどちらかを追加する必要があります。Cache-Control: no-cacheCache-Control: max-age=0no-cacheは、キャッシュをしない設定に見えますが、そうではなく、キャッシュがあ最新かを検証してからキャッシュを利用するという意味です。

検証環境

  • Xcode : 11.2
  • iOS: 13.2.2

検証結果概要

検証結果を以下の図で示します。構成要素は、以下の3つです。

  • iOSアプリ
  • iOS内のキャッシュ
  • Webサーバ
    検証は、上記の図で示した3つのパターンを試します。
  1. キャッシュが存在しない場合、サーバから取得
  2. キャッシュが新しい場合、キャッシュを利用
  3. キャッシュが古い場合、サーバから取得

1と3は、サーバからの応答と同じ内容をアプリで見れます。
しかし、2のキャッシュが新しい場合は、サーバから304 NOT MODIFIED応答が来て、データは転送されません。このため、ネットワークの転送量は少なくなります。しかし、アプリから見る応答は、200 OKと見えます。サーバからデータが転送されているように見えますが、実際にはキャッシュを使っています。

URLSession

この図では、Cache-Control: no-cache Last-Modifiedを利用した例を図示していますが、Cache-Control: max-age=0ETagを利用した場合もキャッシュの利用は同じでしたので、図は省略します。

参考文献

参考資料: 検証結果詳細

以下では、サーバのResponseにCache-Control: no-cacheを入れた場合を最初に載せます。動作は同じでしたが、以下の場合も載せておきます。

  • Cache-Control: no-cache の場合
  • Cache-Control: max-age=0 の場合
  • Cache-Control: no-cache ETagの場合

HTTPヘッダ(Cache-Control: no-cache)の場合

初回アクセス

初回アクセス時のiOSからサーバへのHTTP Request

request.http
GET / HTTP/1.1
Host: example.com

初回アクセス時のサーバからiOSへのHTTP Response

response.http
HTTP/1.1 200 OK
Date: Tue, 12 Nov 2019 02:59:55 GMT
Last-Modifi![20191113-URLSession.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404406/e1dbbef6-9589-4e80-a00d-babdff924194.png)
ed: Wed, 04 Sep 2019 00:00:00 GMT
Cache-Control: no-cache

アプリ(URLSession)で取得できるHTTP Response。このResponseデータがスマホのキャッシュに保存される。

app.http
HTTP/1.1 200 OK
Date: Tue, 12 Nov 2019 02:59:55 GMT
Last-Modified: Wed, 04 Sep 2019 00:00:00 GMT
Cache-Control: no-cache

2回目アクセス(スマホ内のキャッシュ利用)

2回目アクセス時のiOSからサーバへのHTTP Request。キャッシュにLast-Modifiedがある場合は、If-Modified-SinceをRequestに含める。

request.http
GET / HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 04 Sep 2019 00:00:00 GMT

2回目アクセス時のサーバからiOSへのHTTP Response。スマホ内のキャッシュを利用する。キャッシュからDateフィールドを更新してアプリに応答

response.http
![undefined]()
HTTP/1.1 304 NOT MODIFIED
Date: Tue, 12 Nov 2019 03:01:01 GMT
Last-Modified: Wed, 04 Sep 2019 00:00:00 GMT
Cache-Control: no-cache

アプリ(URLSession)で取得できるHTTP Response

app.http
HTTP/1.1 200 OK
Date: Tue, 12 Nov 2019 03:01:01 GMT
Last-Modified: Wed, 04 Sep 2019 00:00:00 GMT
Cache-Control: no-cache

HTTPヘッダ(Cache-Control: max-age=0)の場合

初回アクセス

初回アクセス時のiOSからサーバへのHTTP Request

request.http
GET / HTTP/1.1
Host: example.com

初回アクセス時のサーバからiOSへのHTTP Response

response.http
HTTP/1.1 200 OK
Date: Tue, 12 Nov 2019 03:14:14 GMT
Last-Modified: Wed, 04 Sep 2019 00:00:00 GMT
Cache-Control: max-age=0

アプリ(URLSession)で取得できるHTTP Response。このResponseデータがスマホのキャッシュに保存される。

app.http
HTTP/1.1 200 OK
Date: Tue, 12 Nov 2019 03:14:14 GMT
Last-Modified: Wed, 04 Sep 2019 00:00:00 GMT
Cache-Control: max-age=0

2回目アクセス(スマホ内のキャッシュ利用)

2回目アクセス時のiOSからサーバへのHTTP Request。キャッシュにLast-Modifiedがある場合は、If-Modified-SinceをRequestに含める。

request.http
GET / HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 04 Sep 2019 00:00:00 GMT

2回目アクセス時のサーバからiOSへのHTTP Response。スマホ内のキャッシュを利用する。キャッシュからDateフィールドを更新してアプリに応答

response.http
HTTP/1.1 304 NOT MODIFIED
Date: Tue, 12 Nov 2019 03:15:33 GMT
Last-Modified: Wed, 04 Sep 2019 00:00:00 GMT
Cache-Control: max-age=0

アプリ(URLSession)で取得できるHTTP Response

app.http
HTTP/1.1 200 OK
Date: Tue, 12 Nov 2019 03:15:33 GMT
Last-Modified: Wed, 04 Sep 2019 00:00:00 GMT
Cache-Control: max-age=0

HTTPヘッダ(ETag)の場合

初回アクセス

初回アクセス時のiOSからサーバへのHTTP Request

request.http
GET / HTTP/1.1
Host: example.com

初回アクセス時のサーバからiOSへのHTTP Response

response.http
HTTP/1.1 200 OK
Date: Tue, 12 Nov 2019 06:01:11 GMT
ETag: "7703193a5f4bc6aa199d7d2121684c3f"
Cache-Control: no-cache

アプリ(URLSession)で取得できるHTTP Response。このResponseデータがスマホのキャッシュに保存される。

app.http
HTTP/1.1 200 OK
Date: Tue, 12 Nov 2019 06:01:11 GMT
ETag: "7703193a5f4bc6aa199d7d2121684c3f"
Cache-Control: no-cache

2回目アクセス(スマホ内のキャッシュ利用)

2回目アクセス時のiOSからサーバへのHTTP Request。キャッシュにETagがある場合は、If-None-MatchをRequestに含める。

request.http
GET / HTTP/1.1
Host: example.com
If-None-Match: "7703193a5f4bc6aa199d7d2121684c3f"

2回目アクセス時のサーバからiOSへのHTTP Response。スマホ内のキャッシュを利用する。キャッシュからDateフィールドを更新してアプリに応答

response.http
HTTP/1.1 304 NOT MODIFIED
Date: Tue, 12 Nov 2019 06:02:13 GMT
ETag: "7703193a5f4bc6aa199d7d2121684c3f"
Cache-Control: no-cache

アプリ(URLSession)で取得できるHTTP Response

app.http
HTTP/1.1 200 OK
Date: Tue, 12 Nov 2019 06:02:13 GMT
ETag: "7703193a5f4bc6aa199d7d2121684c3f"
Cache-Control: no-cache
34
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?