高速でシームレスなユーザー体験を提供するためには、すべてのアプリケーションに効果的なキャッシュシステムが必要です。キャッシュがない場合、アセットを繰り返しダウンロードすることで大量のリソースが消費され、読み込み時間が遅延する可能性があります。この問題を解決するために、開発者はパフォーマンスを向上させ、遅延を最小限に抑えるために、ディスクキャッシュLRU(Least Recently Used)キャッシュデータベースキャッシュなど、さまざまなキャッシュ戦略を利用します。


問題:

Beeda Userアプリでは、画像、Lottieアニメーション、ビデオといったメディアアセットを効果的に扱うことが、高速でシームレスなユーザー体験を実現するために不可欠でした。それぞれのメディアアセットには、キャッシュや最適化に関する特有のニーズと課題があります。

  • 画像: 高解像度の画像は大量のメモリとストレージを消費する可能性があるため、ディスクキャッシュ(例: URLCacheやSDWebImageのようなサードパーティライブラリを使用)が重要です。
  • Lottieアニメーション: Lottieファイル(JSONベースのアニメーション)は軽量ですが、効率的なパースとレンダリングが求められます。インメモリキャッシュ(例: NSCache)を使用することで、繰り返しパースする際の負荷を軽減できます。
  • ビデオ: ビデオはサイズが大きいため、ストリーミング部分的なキャッシュが必要で、過剰なメモリやストレージの使用を防ぎます。

各キャッシュ機構が特定のメディアタイプに対して効果的である一方で、次のような重要な課題が浮かび上


実装:

image info

Beeda Userアプリにおけるメディアアセット管理の課題に対処するため、各タイプに応じた異なるユーティリティ(Utils)を設計しました。この構造化されたアプローチにより、画像、Lottieアニメーション、ビデオなどのアセットを効率的に処理できます。

1. ユーティリティレイヤー (Utils Layer)

各メディアアセットタイプには、それぞれのタスク(ダウンロード、パース、処理など)を担当するユーティリティクラスがあります。これらのユーティリティは、アプリが直接操作することなく、アセットマネージャーからアセットを取得する中間役として機能します。

例えば、画像ユーティリティを考えてみましょう。もし画像がすでにキャッシュされていなければ、このユーティリティはアセットマネージャーのgetDataメソッドを呼び出して画像をダウンロードします。ダウンロードが完了すると、ユーティリティは生データをアセットマネージャーに渡して保存します。

protocol ImageUtilProtocol {
    func downloadImage(url: String, imageCompletionHandler: ((UIImage) -> Void)?, storageType: StorageType)
}

2. アセットマネージャー (Assets Manager)

アセットマネージャーはシステム内の中間レイヤーとして機能し、ユーティリティ(Utils)とさまざまなキャッシュメカニズムの間のブリッジとして働きます。主な役割は、指定されたストレージタイプに基づいて、どのキャッシュ方式を使用するかを決定することです。

StorageTypeパラメータは、利用可能なキャッシュメカニズム(例: インメモリ、ディスク、ハイブリッドなど)を定義します。

enum StorageType {
  case lru(LRUCacheType)
  case disk
}
enum LRUCacheType: String {
   case lottie
   case image
   case video
}

アセットマネージャーには、データを保存および取得するための共通メソッドがあります。

protocol AssetsManagerProtocol {
    func writeData(data: Data, forKey key: String, storageType: StorageType)
    func readData(forKey key: String, storageType: StorageType) -> Data?
    func removeData(forKey key: String, storageType: StorageType)
}

アセットマネージャーは、キャッシュ操作の管理において重要な役割を果たします。ユーティリティクラスがそのメソッドを呼び出すと、アセットマネージャーはstorageTypeに基づいて適切なキャッシュレイヤーを決定します。その後、データの書き込み、読み取り、または削除といったタスクを該当するキャッシュメカニズムに委任し、効率的かつ正確にアセットを処理します。

3. キャッシュレイヤー

このレイヤーには、ディスクキャッシュ、LRUキャッシュ、データベースキャッシュなど、私たちが使用するすべてのキャッシュメソッドが含まれています。データはそのままの形式で保存され、特定のデータタイプに依存しません。

私たちは、ユースケースに応じてさまざまなキャッシュメカニズムを採用しています。永続的に保存する必要があるアセットにはディスクキャッシュを使用し、逆に一時的なデータにはLRUキャッシュ方式を利用します。さらに、LRUキャッシュシステム内では、画像、Lottieアニメーションなど、異なるタイプのアセット用に別々のキャッシュを用意しています。

  • ディスクキャッシュ

ディスクキャッシュは、データをディスクに直接保存する方法で、必要に応じて効率的な読み書き操作を可能にします。

  • LRUキャッシュ

最小最近使用(LRU)キャッシュは、固定サイズの制限内でメモリにデータを保存します。メモリが容量に達すると、最も最近アクセスされていないアイテムが新しいデータを収容するために追い出されます。

  • 私たちの場合:
    • 画像の場合: ディスクキャッシュLRUキャッシュの両方を活用し、パフォーマンスとストレージのバランスを取ります。
    • アニメーションのレンダリングの場合: より高速なアクセスと効率的なメモリ使用のために、LRUキャッシュのみを使用します。
    • 動画のレンダリングの場合: 大きな動画ファイルの処理に適したカスタムディスクキャッシュを使用します。

image info

各キャッシュ層は独立して操作し、それぞれの指定されたタスクを担当します。将来的に新しいキャッシュメカニズムを導入する必要がある場合でも、問題なく統合でき、システムに影響を与えることはありません。