Kingfisher 8.15のrespectCacheControlを試した|サーバーのCache-Controlに合わせて画像キャッシュの期限を決める

Kingfisher 8.15.0で、サーバーのCache-Controlに合わせて画像を保存できるようになった

iOSアプリで画像を読み込んで表示するSwiftのライブラリ「Kingfisher」の8.15.0が、2026年11月3日に公開されました(8.15.0のリリースノート)。新しく.respectCacheControlというオプションが入り、画像を返すサーバーのCache-Controlヘッダーに合わせて、画像をキャッシュに保存するか、いつまで保存するかを決められるようになりました。

Kingfisherは、一度ダウンロードした画像をメモリとディスクの2か所に取っておき(キャッシュ)、次からはダウンロードせずに表示します。Cache-Controlは、サーバーが「この画像を保存しないで(no-store)」「何秒まで保存してよい(max-age)」と伝えるHTTPのヘッダーです。これまでのKingfisherはこのヘッダーを見ずに、アプリ側の設定(既定ではディスク7日、メモリ5分)で保存していました。

この記事では、8.14.0と8.15.0を同じコードで動かし、ヘッダーを変えて返すローカルのサーバーから画像を読み込んで、キャッシュの中身と期限を比べました。結果はMacとiOSシミュレーターで同じでした(期限の切れ目に当たった読み込みは、わずかな時刻の差で結果が変わることがあります)。

8.14.0と比べて分かったこと

.respectCacheControlを付けたときの4つの動き

no-store

保存しない

メモリにもディスクにも残しません。同じURLを取り直してno-storeを受け取ると、前に保存した同じ画像も消します。

max-age

max-ageからAgeを引いた秒数で期限切れ

max-age=4、Age=1なら3秒で期限が切れ、4秒後の読み込みでダウンロードし直しました。

上限

アプリの設定より長くならない

ディスクの期限を10秒に設定すると、max-age=3600を受け取っても10秒でした。

注意する点

読み直すと期限より長く残ることがある

何度も読み込む画像は、ディスクから読み直されて期限より長く残ることがあります。追加のオプションで抑えられます。

何も指定しないと、no-storeの画像も7日間キャッシュに残る

まず、ヘッダーごとに画像を1回読み込み、キャッシュに残ったかと、ディスクの期限(保存してから使えなくなるまでの秒数)を調べました。ディスクの期限は、Kingfisherがキャッシュファイルに書く作成日時と更新日時の差から読み取っています。

表の見方: 8.15.0でも、オプションを付けなければ8.14.0と同じく、すべての画像を7日間(604,800秒)保存しました。.respectCacheControlを付けた右の列だけ、ヘッダーに合わせて変わります。
サーバーが返したヘッダー 8.14.0/8.15.0 オプションなし 8.15.0 .respectCacheControl
なし保存、7日保存、7日
no-store保存、7日保存しない
max-age=3600保存、7日保存、3,600秒(1時間)
max-age=0保存、7日保存しない
no-cache保存、7日保存、7日(指示は使われない)

ディスクの期限の既定値は7日、メモリは5分(300秒)。macOS 27.0.1とiOSシミュレーター(iOS 27.2)で、Xcode 27.2を使って2026年11月10日に確認。

no-cacheは「使う前にサーバーへ確かめて」という指示ですが、Kingfisherはこの指示を扱わず、これまでどおり保存します(変更のプルリクエスト(#2609))。オプションが扱う指示はno-storeとmax-ageの2つです。

使い方:読み込みのオプションに.respectCacheControlを1つ足す

まずKingfisherを8.15.0以上に上げます。Swift Package Managerなら、版の条件を次のように8.15.0以上にして、パッケージを更新します。

dependencies: [
    .package(url: "https://github.com/onevcat/Kingfisher.git", from: "8.15.0")
]

8.14.0のまま、オプションの配列に.respectCacheControlを書くと、ビルドで次のエラーになります。このエラーが出たら、版が上がっていないことが分かります。

error: type 'KingfisherOptionsInfoItem' has no member 'respectCacheControl'

オプションは、これまでの読み込みの書き方に1つ足すだけです。UIKitのsetImage、KFのビルダー、SwiftUIのKFImageのどれでも使えます。

// UIKit
imageView.kf.setImage(with: url, options: [.respectCacheControl])

// KF ビルダー
KF.url(url)
    .respectCacheControl()
    .set(to: imageView)

// SwiftUI
KFImage(url)
    .respectCacheControl()

アプリ全体の画像に効かせるなら、KingfisherManager.shared.defaultOptionsへ入れます。個別に指定しなくても、no-storeの画像は保存されませんでした。

// アプリの起動時に一度だけ
KingfisherManager.shared.defaultOptions += [.respectCacheControl]

全体に入れると、サーバーがmax-ageを短く付けている画像は、これまでより早くダウンロードし直すようになります。通信量やサーバーへのリクエスト数が気になる場合は、署名付きURLの画像や頻繁に変わるプロフィール画像など、ヘッダーを守らせたい画像だけに付けます。

期限はmax-ageからAgeを引いた秒数で、アプリの設定より長くならない

Ageは、レスポンスが元のサーバーで作られてから、CDNなどの途中のキャッシュサーバーを経て届くまでに経った秒数の目安を示すヘッダーです。Kingfisherはmax-ageからAgeを引いた残りの秒数を、その画像の期限の上限にします。max-age=4、Age=1を返す画像で、時間の経過とキャッシュの状態を追いました。

  1. 保存した直後

    メモリとディスクの両方に保存されました。ディスクの期限は3秒(4 − 1)です。

  2. 4秒後

    メモリにもディスクにも、使える画像は残っていませんでした。

  3. もう一度読み込む

    サーバーから2回目のダウンロードがありました。オプションなしでは、メモリのキャッシュから返り、ダウンロードは1回のままでした。

サーバーが長いmax-ageを付けても、アプリの設定より長くは保存しません。ディスクとメモリの期限を10秒に設定したキャッシュでmax-age=3600の画像を読み込むと、ディスクの期限は10秒でした(リリースノートの説明どおり)。既定の設定(ディスク7日)なら、表のとおり3,600秒になります。

max-age=0の画像と、max-age=3・Age=5のように残りが0秒以下になる画像は、no-storeと同じく保存されませんでした。

.respectCacheControlなら、no-storeを受け取ったときに前の画像も消える

同じURLの画像が、1回目はヘッダーなしで返り、2回目(キャッシュを使わずに取り直す.forceRefreshを付けた読み込み)はno-storeで返る場合を試しました。サーバー側で画像を「もう保存しないで」に切り替えた場面にあたります。

オプションなし(8.14.0と8.15.0)

2回目にダウンロードした画像も、7日の期限で保存されました。

8.15.0 .respectCacheControl

2回目のno-storeを受け取った時点で、1回目の画像もメモリとディスクから消えました。

消えるのは、アプリが同じURLを取り直してno-storeを受け取ったときです。キャッシュに画像が残っている間、Kingfisherはサーバーに問い合わせずにキャッシュから表示します。.forceRefreshを付けずに2回目を読み込むと、画像はメモリから返り、サーバーへのリクエストは1回のままでした。サーバー側を切り替えた画像が手元から消えるのは、キャッシュの期限が切れて取り直したときか、.forceRefreshで取り直したときです。消えるのは、その読み込みと同じ加工(大きさなど)で保存した画像です。no-storeを受け取った読み込みにも.cacheOriginalImageを付けていれば、元画像も消えます。32ピクセルに縮小して元画像も保存した画像で試すと、no-storeを受け取ったあと、どちらも消えました。

何度も読み込む画像は、max-ageより長く残ることがある

同じURLを何度も読み込むアプリでは、ディスクに保存した画像をもう一度読むことで、max-ageで決まった期限より長くキャッシュに残る経路が2つあります。ディスクから読み直すのは、メモリの期限(既定5分)が切れたあと、アプリを起動し直したあと、メモリ不足の通知でメモリのキャッシュが消えたあとなどです。

ディスクから読むたびに、ディスクの期限が延びる

Kingfisherは、ディスクのキャッシュから画像を読むたびに、その画像のディスクの期限を延ばします。既定の.cacheTimeは、読んだ時点から保存したときと同じ長さだけ延ばす設定です(diskCacheAccessExtendingExpirationの既定値)。.respectCacheControlで決まったディスクの期限も、この延長の対象です(変更のプルリクエスト(#2609))。

max-ageがメモリの期限より長い画像では、これが普通に起きます。メモリの期限を3秒、max-age=10にして7秒おきに読み込むと、2回目以降の読み込みはどれもディスクから返り、そのたびにディスクの期限が延びて、約28秒までダウンロードは1回でした。.diskCacheAccessExtendingExpiration(.none)を足すと、14〜15秒後と28〜29秒後にダウンロードし直しました。既定のメモリの期限は5分なので、max-age=3600の画像でも、ダウンロードから5分を過ぎて読み直すとディスクから返り、その時点からディスクの期限が1時間延びます。1時間以内の間隔で読み込みを続ける画像は、キャッシュから返り続けます。

期限3秒(max-age=4、Age=1)の画像でも確かめました。保存して2秒後にメモリを空にしてディスクから読み、保存から4秒後のディスクの状態を見ました。

既定(.cacheTime)

4秒後もディスクに残った

2秒後に読んだ時点で、期限がその3秒後(保存から5秒後)まで延びたためです。

.diskCacheAccessExtendingExpiration(.none)

4秒後はディスクで期限切れ

読んでもディスクの期限が延びず、保存から3秒で切れました。

ディスクから読んだ画像は、メモリにアプリの設定の期限で入り直す

ディスクから読んだ画像は、次の表示を速くするためにメモリにも入り直します。このとき付くメモリの期限は、アプリの設定の期限(既定5分)です(変更のプルリクエスト(#2609))。ディスクから入り直したこのメモリの期限は、既定ではメモリから読むたびに延びます(memoryCacheAccessExtendingExpirationの既定値が.cacheTime)。ダウンロードして保存したときに付くメモリの期限(max-ageから決まる秒数と、アプリの設定の短いほう)は固定で、読んでも延びません。

max-ageがメモリの期限より短い画像でも、期限の切れ目でこの入り直しが起きることがあります。Kingfisherはディスクの期限を秒単位に切り上げて記録するので、max-ageから決まったメモリの期限が切れたあと、1秒未満のあいだディスクの期限が残ることがあるためです(8.15.0のソースで確認)。.respectCacheControlだけを付けて期限3秒の画像を0.25秒ごとに読み込むと、MacでもiOSシミュレーターでも、期限の切れ目の読み込みがディスクから返り、そのあと約8秒まで読み込みを続けた間はメモリから返り続けた回がありました(切れ目の読み込みがダウンロードになった回もあります)。何度も読み込む画像は、.respectCacheControlだけではmax-ageの期限で取り直されないことがあります。

メモリを空にして試した場合も同じです。.diskCacheAccessExtendingExpiration(.none)を付けた画像を2秒後にディスクから読み、そのままメモリに残しておくと、保存から5秒後(max-ageから決まった期限の2秒後)の読み込みはメモリから返り、ダウンロードし直しませんでした。

メモリに入り直す分をどう抑えられるかを、期限3秒(max-age=4、Age=1)の画像を2秒後にディスクから読み、その後1秒ごとに読み込んで比べました。

3つのオプション(メモリの期限2秒)

9秒後もメモリから返った

.memoryCacheExpiration(.seconds(2))を足しても、読むたびにその時点から2秒後まで延びるため、ダウンロードは最初の1回だけでした。

4つのオプション(メモリの延長も止める)

メモリの期限の2秒で切れた

.memoryCacheAccessExtendingExpiration(.none)も足すと、4秒後と8秒後にダウンロードし直しました。

メモリに入れない(.expired)

max-ageの期限で切れた

.memoryCacheExpiration(.expired)にして0秒から1秒ごとに読み込むと、2秒まではディスクから返り、期限の3秒を過ぎた最初の読み込み(3〜4秒後)でダウンロードし直しました。

max-ageを過ぎてから残る時間を決まった秒数以内に抑えたい場合は、4つのオプションを組み合わせます。max-ageを過ぎてから残るのは、最長でメモリの期限の分(コードでは30秒)です。そのかわり、何度も読み込む画像も、メモリの期限が切れるたびにディスクから読み直すか、max-ageを過ぎていればダウンロードし直します。

imageView.kf.setImage(
    with: url,
    options: [
        .respectCacheControl,
        .diskCacheAccessExtendingExpiration(.none),
        .memoryCacheExpiration(.seconds(30)),
        .memoryCacheAccessExtendingExpiration(.none)
    ]
)

max-ageの期限で使えなくしたい場合は、メモリに画像を入れない.memoryCacheExpiration(.expired)を使います(ディスクの期限は秒単位に切り上げて記録されるので、切れるのが1秒未満遅れることがあります)。そのかわり、表示のたびにディスクから読み込むことになります。

imageView.kf.setImage(
    with: url,
    options: [
        .respectCacheControl,
        .diskCacheAccessExtendingExpiration(.none),
        .memoryCacheExpiration(.expired)
    ]
)

元画像から作り直したサムネイルは、アプリの設定の期限で保存される

Kingfisherでは、.processorに縮小などの加工(例: ResizingImageProcessor)を指定すると加工した画像を保存し、.cacheOriginalImageを付けると加工前の画像(元画像)も保存できます。max-age=3600の画像で試しました。

  • 1回目(32ピクセルに縮小、元画像も保存): 縮小した画像も元画像も、期限は3,600秒でした。
  • 2回目(同じURLを16ピクセルに縮小): ダウンロードせず、保存済みの元画像から作り直しました。この16ピクセルの画像は、アプリの設定の期限(既定7日)で保存されました。2回目の読み込みに.diskCacheExpiration(.seconds(60))を付けると、期限は60秒になりました。

リリースノートにも、max-ageの上限が効くのはそのダウンロードで保存した画像だけで、キャッシュ済みの元画像からあとで加工した画像は通常の期限になる、と書かれています。元画像を保存していて、一覧と詳細で大きさを変えて表示するアプリでは、作り直した側の画像がmax-ageより長く残ります。

導入前に確かめる3つのこと

  1. 1. 画像のサーバーが返すCache-Controlを確かめる

    ブラウザーの開発者ツールやcurl -s -D - -o /dev/null 画像のURL(画像を取得してヘッダーだけを表示)で、レスポンスにno-storeやmax-ageが付いているかを見ます。付いていなければ、オプションを入れても動きは変わりません。

  2. 2. 全体に入れるか、特定の画像だけにするかを決める

    全体ならdefaultOptions、特定の画像なら読み込みごとに.respectCacheControlを付けます。短いmax-ageの画像が多いと、ダウンロードの回数が増えます。

  3. 3. 期限をどこまで厳密にするかを決める

    max-ageを過ぎて残る時間を抑えるなら、ディスクとメモリの両方で延長を止め、メモリの期限を短くする4つのオプションを使います。max-ageの期限で切りたいなら.memoryCacheExpiration(.expired)でメモリに入れません。元画像から大きさを変えて作り直す画像はアプリの設定の期限になるので、その読み込みに.diskCacheExpirationを付けて短くします。

参考資料

検証環境

  • macOS 27.0.1(Apple Silicon)、Xcode 27.2(Swift 6.4.2)
  • iOSシミュレーター iOS 27.2(iPhone 17)
  • Kingfisher 8.14.0と8.15.0(Swift Package Manager)
  • 検証日: 2026年11月10日

この記事は、AIが調査と下書きを担当し、上記の環境で実際に動かして結果を確かめ、下書きとは別の担当が公開前に本文と出典を確認しています。制作と訂正の方針は編集・訂正方針で公開しています。