写真表示が重かった原因を追って、自分の言葉で腹落ちさせた学習メモです。
綴Photo の写真表示ウィジェット AppPhotoImage には、thumbnailLogicalSize というサイズのヒントを渡せます。これが有るか無いかで、画像を「どこから・どう取ってくるか」がまるごと変わっていました。サムネで足りる表示なのに、無指定だとフル解像度を掴みにいっていました。まとめておく。
サイズを渡すと、OSのサムネを使う
thumbnailLogicalSize を渡すと、OS が写真アプリのグリッド用にあらかじめ持っている縮小版を取りにいきます。ピクセルサイズは thumbnailLogicalSize × devicePixelRatio で決めます。devicePixelRatio は画面の1点に実際に何個の物理ピクセルが詰まっているかの倍率で、高精細画面だと2〜3倍になります。
渡さないと、フル解像度を自前デコード
thumbnailLogicalSize が無いと、経路が裏返ります。
どれくらい違うのか。iPhone の標準的な12MP写真(4032 × 3024)を、一覧に並ぶくらいの大きさ(180pt 幅・3倍の高精細画面)で出す場合の概算がこれ。
| フル解像度 | OSサムネ | |
|---|---|---|
| 画素数 | 4032 × 3024 | 540 × 405 |
| JPEGファイル | 約 2〜3 MB | 約 30〜60 KB |
| 展開後のビットマップ | 48.8 MB | 0.87 MB |
ここが引っかかりやすいところで、「数MB」はファイルの大きさであって、メモリに載せたときの大きさではありません。JPEG は圧縮された状態なので、画面に出すために展開すると1画素4バイトで 4032 × 3024 × 4 ≒ 48.8 MB になります。ファイルサイズの感覚で「数MBか」と思っていると、15倍ほど過小に見積もることになります。
ただし綴Photo で実際に抜けていたのは thumbnailLogicalSize だけで、decodeLogicalSize のほうは付いていた。展開後のサイズはそちらで抑えられていたので、メモリがここまで振り切れていたわけではない。毎回払っていたのは、数MBのファイルをディスクから読み直し、OSが既に作ってあるサムネを使わずに JPEG を展開し直すぶんだ。iOS では実体ファイルを要求した時点で tmp コピーも走りえます。
秒数は計測していないので何秒縮んだかは言えない。ただ「出来合いのものがあるのに毎回作り直している」構図だったのは確かだった。
直した方向
綴Photo は実装を AI に手伝ってもらいながら進めていて、decodeLogicalSize だけ有って thumbnailLogicalSize が抜けていた箇所には、AI に thumbnailLogicalSize を足してもらった(detail_layout_album / detail_layout_letter / tsuzuru_photo_stage)。何がどのレイヤで効くのかを切り分けて揃えるところは、自分で確かめている。
引っかかっていたのは、この2つを混同していたことだった。
「どちらか書けばいい」と誤解しやすいです。どちらも省略可能な引数なので、片方だけ書いても何のエラーも出ず動いてしまいます。重いまま表示されるだけなので気づきにくいです。
学びポイント
- サムネ表示にフル解像度を掴むと、表示に要らない重さを毎回払います。サイズヒントを渡してOSのサムネキャッシュに乗せます
decodeLogicalSize(デコード時のリサイズ)とthumbnailLogicalSize(取得経路の切替)は別レイヤ。名前が似ていて混同しやすいので、何を変えるパラメータなのかで見分けます- iOSは実体ファイルを要求すると tmp コピーが走り得ます。「取得経路」を意識すると、隠れたコピーや通信に気づけます
おわりに
実装をAIに手伝ってもらいながら後から読み解く中で、最初は「画像が重い」としか分からなかった症状が、thumbnailLogicalSize と decodeLogicalSize を別物として分けて捉えた時に経路の違いとして見えた。
この記事は2026年6月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。