モバイル開発におけるLazy Loading — 仕組み、原則、実装方法

著者: IT Sectr 公開日: 2026-04-01 読了時間: 9 分

Lazy Loadingは、アプリ起動時ではなく、ユーザーが実際に必要とした時点でリソースを要求する、データ、画像、コンポーネントの遅延読み込み戦略です。Android Paging 3ガイドによると、リストの遅延読み込みは、大規模データセットを扱う際のメモリ消費を60~80%削減します。遅延初期化は、すべてのLazy Loading実装の基礎となる重要な原則です。

重要ポイント

  • Lazy Loadingはメモリを節約し、初回起動を高速化する遅延読み込みパターン
  • LazyVStackLazyHStackは、SwiftUIの遅延リスト用組み込みコンポーネント
  • Paging 3は、APIとデータベースからのページ区切りデータ読み込みのためのAndroidライブラリ
  • 画像ライブラリ(Glide、Coil、Kingfisher)は、画面に表示されたときのみ画像を読み込む
  • プリローディングは先行読み込みであり、スムーズなUXのためのLazy Loadingの反対側の側面

Lazy Loadingとは

Lazy Loading(遅延読み込み)は、アプリケーションのリソースを起動時ではなく、使用直前に読み込む設計および最適化パターンです。モバイル開発では、Lazy Loadingはデータ(リストのページネーション)、画像(スクロール時の読み込み)、コンポーネント(遅延スタックとビュー)の3つの主要カテゴリに適用されます。

Lazy Loadingの逆はEager Loading(即時読み込み)で、すべてのリソースが画面起動時に読み込まれます。Eager Loadingは実装は簡単ですが、より多くのメモリを消費し、最初の表示時間が長くなります。数千の項目を持つリストの場合、Eager Loadingはメモリ制限のあるデバイスでOOM(メモリ不足)を引き起こします。Lazy Loadingは、画面に表示されているものだけを読み込み、残りはユーザーがスクロールするにつれて読み込むことでこの問題を解決します。

iOSとAndroidのコンテキストでは、Lazy Loadingは異なるレベルで実装されています。SwiftUIは遅延レンダリングのためにLazyVStackとLazyHStackを提供します。UIKitはdequeueReusableCellを使用してUITableViewを使用します。AndroidはViewHolderプールを使用してRecyclerViewを使用します。データレベルでは、RoomとPaging 3、Core DataとNSFetchedResultsControllerがあります。特定の技術の選択は、スタックとパフォーマンス要件に依存します。

遅延読み込みの動作原理

中心的な原則は、現在の画面状態に必要な正確な量のデータに加えて、スムーズなスクロールのための先行バッファを読み込むことです。このアプローチは、可視性追跡と要素の仮想化という2つのメカニズムに基づいています。

可視性追跡

追跡メカニズムは、どの要素が画面の可視領域(ビューポート)にあるかを判断します。Androidでは、LinearLayoutManagerまたはGridLayoutManagerがfindFirstVisibleItemPositionおよびfindLastVisibleItemPositionメソッドを通じてこれを行います。iOSでは、UIScrollViewが可視領域を計算するためにbounds.origin.yとcontentOffset.heightを提供します。要素がビューポート(またはプリフェッチバッファ)に入ると、その読み込みが開始されます。要素が画面から離れると、そのリソースは解放されるか、キャッシュに移動されます。

kotlin
// Android — RecyclerViewでの可視性追跡
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        val layoutManager = recyclerView.layoutManager as LinearLayoutManager
        val lastVisible = layoutManager.findLastVisibleItemPosition()
        val totalCount = layoutManager.itemCount
        // 残りがある場合、次のページを読み込む < 5個の要素
        if (lastVisible >= totalCount - 5) {
            loadMoreItems()
        }
    }
})

バッファリングとプリフェッチ

プリフェッチバッファは、まもなく画面に表示される要素の先行読み込みです。RecyclerViewはlayoutManager.setItemPrefetchEnabled(true)を通じてGapWorker.Prefetchをサポートします。iOS UITableViewはUITableViewDataSourcePrefetchingを通じてプリフェッチをサポートします。プリフェッチバッファのサイズは通常1~2画面先で、スクロールの滑らかさとメモリ消費の妥協点を提供します。大きすぎるプリフェッチバッファはLazy Loadingの利点を無効にし、小さすぎるバッファは高速スクロール時に空白を作成します。

画像のLazy Loading

画像はモバイルアプリケーションにおいて最も重いリソースタイプです。1枚の12MP写真は、非圧縮で3~5MBを占めることがあります。画像のLazy Loadingは、何百もの非表示画像がメモリに読み込まれるのを防ぎます。これはユーザーアバターや商品カタログを含むリストにとって致命的です。

Android用ライブラリ:GlideとCoil

Glideは、キャッシング、変換、アニメーションをサポートするAndroid用の最も人気のある画像読み込みライブラリです。Coilはより軽量な代替品で、コルーチンを使用してKotlinで書かれています。どちらのライブラリも、ImageViewが画面から離れると自動的に読み込みを一時停止し、ViewHolderが再利用されるとリクエストをキャンセルします。Coilはコルーチンを使用し、サイズは約1.5MBで、Glideの約4MBと比較して、APKサイズに焦点を当てたプロジェクトに適しています。

kotlin
// Coil — 遅延画像読み込み
imageView.load("https://example.com/image.jpg") {
    crossfade(true)
    placeholder(R.drawable.placeholder)
    size(512, 512)
    memoryCachePolicy(CachePolicy.ENABLED)
}

iOS用ライブラリ:KingfisherとSDWebImage

Kingfisherは、Swift Concurrency、ディスクおよびメモリキャッシング、UICollectionViewのプリフェッチをサポートするiOS用ライブラリです。SDWebImageはObjective-Cにルーツを持つ古いライブラリですが、Swiftをサポートしています。どちらのライブラリもUIImageViewと統合し、読み込みライフサイクルを自動的に管理します:セルが再利用されるとリクエストをキャンセルし、セルが表示されているときのみ画像を読み込み、メモリ不足通知でメモリを解放します。

データとリストのLazy Loading

大規模なデータリストは、モバイルアプリにおけるLazy Loadingの主な適用分野です。ニュースフィード、商品カタログ、チャット、取引履歴 — 潜在的に無限のリストを持つ画面はすべて、ページネーションと遅延読み込みを必要とします。

Android用Paging 3

Paging 3はAndroid Jetpackのライブラリで、RemoteMediator(API + データベース)からのデータ要求、Roomでのキャッシング、PagingDataによるページごとの出力、AsyncPagingDataAdapterによる表示という完全な遅延読み込みサイクルを実装します。Paging 3は3種類のページネーションをサポートしています:ページベース、アイテムベース(オフセット/制限)、キーベース(APIからのページネーションキー)。Separatorは、読み込みインジケーターのためのページ間セパレーターの組み込みサポートです。

kotlin
// Paging 3 — APIからの遅延読み込み
class ArticlePagingSource(
    private val api: ArticleApi
) : PagingSource<Int, Article>() {

    override suspend fun load(
        params: LoadParams<Int>
    ): LoadResult<Int, Article> {
        return try {
            val page = params.key ?: 1
            val response = api.getArticles(page)
            LoadResult.Page(
                data = response.items,
                prevKey = page.takeIf { it > 1 }?.dec(),
                nextKey = page + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}

SwiftUI LazyVStackとLazyHStack

LazyVStackはSwiftUIの組み込みコンテナで、要素が画面に表示されたときのみ作成およびレンダリングを行います。すべての子要素のレイアウトを即座に計算するVStackとは異なり、LazyVStackは要素が表示されるかプリフェッチ範囲に入るまでビューの作成を延期します。LazyHStackはカルーセル用の水平版です。大規模リストの場合、AppleはListの使用を推奨しており、Listは内部で追加の組み込みリサイクル機能を備えたLazyVStackと同様に動作します。

swift
// SwiftUI — 遅延読み込みを使用したLazyVStack
ScrollView {
    LazyVStack(spacing: 8) {
        ForEach(articles) { article in
            ArticleRow(article: article)
                .onAppear {
                    if article == articles.last {
                        viewModel.loadMore()
                    }
                }
        }
    }
}

UIコンポーネントのLazy Loading

UIコンポーネントの遅延読み込みは、インターフェースの一部(ヘッダー、フッター、設定セクション、タブ)を画面起動時ではなく、最初のアクセス時に作成する技術です。これにより、初期レンダリングが高速化され、メインスレッドの負荷が軽減されます。

Android:ViewStubとFragmentの遅延読み込み

ViewStubはAndroidの軽量なViewプレースホルダーで、レイアウトのスペースを取らず、inflate()が呼び出されるまで子Viewを作成しません。検索パネル、詳細設定、広告ブロックなど、めったに使用されないセクションに最適です。Fragmentの遅延読み込みは、ユーザーがそのタブに切り替えるまでFragment.onCreateViewを延期する技術です。ViewPagerのisVisibleまたはUserVisibleHintを介して実装されます。

AndroidXは、オンデマンドでのモジュールの遅延読み込みのためのSplitInstallManagerを提供します。セットアップ、診断、追加機能モジュールは、ユーザーの最初の要求時にのみDynamic Featureモジュールとして読み込まれます。これにより、ベースアプリケーションのサイズが30~50%削減され、同時にデータレベルだけでなくコードレベルでもLazy Loadingの原則が実装されます。

iOS:TabViewとViewBuilderの遅延性

TabViewはSwiftUIで各タブのコンテンツを遅延読み込みします — タブがアクティブになったときのみです。UIKit UITabBarControllerはデフォルトで起動時にすべての子コントローラーを作成しますが、これらをすぐにtabBarController.viewControllersに追加せず、ユーザーが切り替えるにつれて置き換えることでこの動作を変更できます。動的に追加されるarrangedSubviewsを持つUIStackViewもLazy Loadingの原則に従います — ユーザーがインターフェースのその部分を必要とするアクションを実行したときにのみSubviewを追加します。

全体的な画面読み込みを最適化するには、すべてのレベルでLazy Loadingを組み合わせます:めったに使用されないセクションにはViewStub、データにはPaging 3、画像にはGlide/Coil、Hilt/Dagger ScopesまたはSwinjectによる遅延ViewModel初期化。このアプローチにより、3GBのRAMを搭載した低価格デバイスでも200~400ミリ秒で読み込まれる画面を実現できます。

よくある質問

Lazy Loadingを使用すべきでない場合は?

画面が少数の要素(20まで)しか表示せず、すべてが即座に必要な場合、Lazy Loadingは過剰です。めったにスクロールされないリストの場合、Eager Loadingはパフォーマンスの顕著な低下なしに、よりシンプルで高速に実装できます。

Lazy Loadingはメモリにどのように影響しますか?

大規模リストの場合、表示要素とプリフェッチバッファのみがメモリに保存されるため、ピークメモリ消費を3~10倍削減します。ただし、プリフェッチと画像キャッシングを追加すると、管理が必要な中程度のメモリ消費が発生します。

SwiftUIでLazyVStackとListのどちらを選ぶべきですか?

Listは、スワイプ、ドラッグ&ドロップ、組み込み選択をサポートする均質なデータに適しています。LazyVStackは、異なるセルタイプ、セクション、非標準の間隔を持つカスタムレイアウト用です。Listは内部的に追加機能を備えたLazyVStackのように動作します。

遅延読み込みの問題をデバッグするには?

Androidでは、Layout Inspectorを使用してView階層を確認します:Lazy Loadingでは、ほとんどの要素がツリーに存在しないはずです。iOSでは、Xcode Debug View Hierarchyを使用します。スクロール中にすべての要素が存在する場合、Lazy Loadingは機能していません。

読み込み速度とメモリ節約のどちらが重要ですか?

両方のパラメータが重要ですが、優先順位はプラットフォームに依存します。ARCと効率的なメモリ管理を備えたiOSでは、読み込み速度が優先されます。JVMとGCを備えたAndroidでは、メモリ節約が優先されます。メインスレッドでの各割り当てがGCフリーズを引き起こす可能性があるためです。

まとめ

  • Lazy Loadingは初回起動を高速化しメモリを節約する遅延読み込みパターン
  • 画像はビューポートに入ったときにのみGlide、Coil、Kingfisherを介して読み込まれる
  • Paging 3はRoomキャッシングによるページ区切りデータ読み込みを提供
  • LazyVStackはSwiftUIで画面に表示されるまで要素のレンダリングを延期
  • ViewStubはAndroidで最初のアクセス時にUIコンポーネントを遅延読み込み
  • プリフェッチバッファはスムーズなスクロールのための先行読み込み
  • すべてのレベルでLazy Loadingを組み合わせる:データ + 画像 + UI

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください