AwesomeCache

SwiftiOSキャッシュライブラリモバイル開発ディスクキャッシュ

ライブラリ

AwesomeCache

概要

AwesomeCacheは、Swiftで書かれたiOS向けの高機能なディスクキャッシュライブラリです。

詳細

AwesomeCacheは、iOS開発者Alexander Schuchによって開発されたSwift製の軽量でパフォーマンスに優れたキャッシュライブラリです。メモリとディスクの両方をサポートし、NSCacheによるメモリキャッシングとファイルシステムによる永続的なディスクキャッシングを提供します。各キャッシュオブジェクトに個別の有効期限を設定でき、自動的な期限切れ管理を行います。同期APIを採用することで、キャッシュの内容について推論しやすい設計となっており、内部的には並行ディスパッチキューを使用してスレッドセーフな読み書きを実現しています。非同期ブロックのキャッシングにも対応し、APIレスポンスや重い計算処理の結果を効率的にキャッシュできます。CocoaPods、Carthage、手動インストールなど複数のインストール方法をサポートし、iOS開発における標準的なキャッシングソリューションとして広く利用されています。

メリット・デメリット

メリット

  • 同期API: 簡潔で理解しやすいシンプルなインターフェース
  • ディスク永続化: アプリ再起動後もデータが保持される
  • 個別有効期限: オブジェクトごとの柔軟な期限設定
  • スレッドセーフ: 並行アクセスに対する安全性
  • 軽量: 最小限の依存関係と小さなフットプリント
  • Swift最適化: Swift言語の特性を活かした設計
  • 非同期対応: 重い処理のキャッシングサポート

デメリット

  • iOS限定: iOS以外のプラットフォームでは利用不可
  • 同期API制限: 大きなデータ処理でUIブロックの可能性
  • ディスク容量: 永続化によるストレージ使用量増加
  • 設定オプション: 他のライブラリと比べて設定項目が限定的
  • コミュニティ: 比較的小さな開発者コミュニティ

主要リンク

書き方の例

インストール方法

# Podfile (CocoaPods)
pod 'AwesomeCache', '~> 5.2'
# Cartfile (Carthage)
github "aschuch/AwesomeCache" ~> 5.2

基本的なキャッシュ操作

import AwesomeCache

// キャッシュの初期化
do {
    let cache = try Cache<NSString>(name: "userCache")
    
    // データの保存
    cache["username"] = "john_doe"
    cache["email"] = "[email protected]"
    
    // データの取得
    if let username = cache["username"] {
        print("ユーザー名: \(username)")
    }
    
    // データの削除
    cache["username"] = nil
    
} catch {
    print("キャッシュの初期化に失敗: \(error)")
}

有効期限付きキャッシュ

import AwesomeCache

class UserDataCache {
    private let cache: Cache<NSData>
    
    init() throws {
        cache = try Cache<NSData>(name: "userData")
    }
    
    func storeUserData(_ data: Data, forKey key: String, expiryTime: TimeInterval = 3600) {
        let nsData = data as NSData
        cache.setObject(nsData, forKey: key, expires: .date(Date().addingTimeInterval(expiryTime)))
    }
    
    func getUserData(forKey key: String) -> Data? {
        return cache.object(forKey: key) as Data?
    }
    
    func removeUserData(forKey key: String) {
        cache.removeObject(forKey: key)
    }
    
    func clearExpiredData() {
        cache.removeExpiredObjects()
    }
}

APIレスポンスのキャッシュ

import AwesomeCache
import Foundation

struct ApiResponse: Codable {
    let data: [String: Any]
    let timestamp: Date
    
    enum CodingKeys: String, CodingKey {
        case data, timestamp
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.timestamp = try container.decode(Date.self, forKey: .timestamp)
        // データのデコード処理
        self.data = [:]
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(timestamp, forKey: .timestamp)
        // データのエンコード処理
    }
}

class ApiCache {
    private let cache: Cache<NSData>
    private let encoder = JSONEncoder()
    private let decoder = JSONDecoder()
    
    init() throws {
        cache = try Cache<NSData>(name: "apiCache")
    }
    
    func cacheApiResponse<T: Codable>(_ response: T, forKey key: String, expiryTime: TimeInterval = 600) {
        do {
            let data = try encoder.encode(response)
            let nsData = data as NSData
            cache.setObject(nsData, forKey: key, expires: .date(Date().addingTimeInterval(expiryTime)))
        } catch {
            print("APIレスポンスのキャッシュに失敗: \(error)")
        }
    }
    
    func getCachedApiResponse<T: Codable>(_ type: T.Type, forKey key: String) -> T? {
        guard let data = cache.object(forKey: key) as Data? else {
            return nil
        }
        
        do {
            return try decoder.decode(type, from: data)
        } catch {
            print("キャッシュからのデコードに失敗: \(error)")
            return nil
        }
    }
}

画像キャッシュの実装

import AwesomeCache
import UIKit

class ImageCache {
    private let memoryCache = NSCache<NSString, UIImage>()
    private let diskCache: Cache<NSData>
    
    init() throws {
        diskCache = try Cache<NSData>(name: "imageCache")
        
        // メモリキャッシュの設定
        memoryCache.countLimit = 100
        memoryCache.totalCostLimit = 50 * 1024 * 1024 // 50MB
    }
    
    func setImage(_ image: UIImage, forKey key: String, expiryTime: TimeInterval = 86400) {
        // メモリキャッシュに保存
        memoryCache.setObject(image, forKey: key as NSString)
        
        // ディスクキャッシュに保存
        if let data = image.pngData() {
            let nsData = data as NSData
            diskCache.setObject(nsData, forKey: key, expires: .date(Date().addingTimeInterval(expiryTime)))
        }
    }
    
    func getImage(forKey key: String) -> UIImage? {
        // まずメモリキャッシュを確認
        if let image = memoryCache.object(forKey: key as NSString) {
            return image
        }
        
        // ディスクキャッシュから取得
        if let data = diskCache.object(forKey: key) as Data?,
           let image = UIImage(data: data) {
            // メモリキャッシュにも保存
            memoryCache.setObject(image, forKey: key as NSString)
            return image
        }
        
        return nil
    }
    
    func removeImage(forKey key: String) {
        memoryCache.removeObject(forKey: key as NSString)
        diskCache.removeObject(forKey: key)
    }
    
    func clearMemoryCache() {
        memoryCache.removeAllObjects()
    }
    
    func clearDiskCache() {
        diskCache.removeAllObjects()
    }
}

非同期処理との組み合わせ

import AwesomeCache
import Foundation

class DataFetcher {
    private let cache: Cache<NSData>
    private let urlSession = URLSession.shared
    
    init() throws {
        cache = try Cache<NSData>(name: "dataFetcher")
    }
    
    func fetchData(from url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
        let cacheKey = url.absoluteString
        
        // キャッシュから確認
        if let cachedData = cache.object(forKey: cacheKey) as Data? {
            completion(.success(cachedData))
            return
        }
        
        // ネットワークから取得
        urlSession.dataTask(with: url) { [weak self] data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let data = data else {
                completion(.failure(NSError(domain: "DataFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "No data received"])))
                return
            }
            
            // キャッシュに保存
            let nsData = data as NSData
            self?.cache.setObject(nsData, forKey: cacheKey, expires: .date(Date().addingTimeInterval(3600)))
            
            completion(.success(data))
        }.resume()
    }
}

キャッシュ統計とモニタリング

import AwesomeCache

class CacheManager {
    private var caches: [String: Any] = [:]
    
    func createCache<T>(name: String, type: T.Type) throws -> Cache<T> {
        let cache = try Cache<T>(name: name)
        caches[name] = cache
        return cache
    }
    
    func getCacheInfo() -> [String: [String: Any]] {
        var info: [String: [String: Any]] = [:]
        
        for (name, cacheObj) in caches {
            if let cache = cacheObj as? Cache<NSData> {
                info[name] = [
                    "objectCount": getObjectCount(cache: cache),
                    "diskSize": getDiskSize(cache: cache),
                    "lastAccessed": getLastAccessTime(cache: cache)
                ]
            }
        }
        
        return info
    }
    
    private func getObjectCount<T>(cache: Cache<T>) -> Int {
        // キャッシュ内のオブジェクト数を取得
        // AwesomeCacheには直接的なカウント機能がないため、実装が必要
        return 0
    }
    
    private func getDiskSize<T>(cache: Cache<T>) -> Int64 {
        // ディスク使用量を計算
        let cacheDirectory = cache.cacheDirectory
        var size: Int64 = 0
        
        if let enumerator = FileManager.default.enumerator(at: cacheDirectory, includingPropertiesForKeys: [.fileSizeKey]) {
            for case let fileURL as URL in enumerator {
                do {
                    let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey])
                    size += Int64(resourceValues.fileSize ?? 0)
                } catch {
                    print("ファイルサイズの取得に失敗: \(error)")
                }
            }
        }
        
        return size
    }
    
    private func getLastAccessTime<T>(cache: Cache<T>) -> Date? {
        // 最後のアクセス時間を取得
        // 実装が必要
        return nil
    }
    
    func clearAllCaches() {
        for (_, cacheObj) in caches {
            if let cache = cacheObj as? Cache<NSData> {
                cache.removeAllObjects()
            }
        }
    }
}

// 使用例
do {
    let cacheManager = CacheManager()
    let userCache = try cacheManager.createCache(name: "users", type: NSData.self)
    let imageCache = try cacheManager.createCache(name: "images", type: NSData.self)
    
    // キャッシュ情報の表示
    let info = cacheManager.getCacheInfo()
    for (name, details) in info {
        print("Cache: \(name)")
        print("  Objects: \(details["objectCount"] ?? 0)")
        print("  Disk Size: \(details["diskSize"] ?? 0) bytes")
    }
    
} catch {
    print("キャッシュマネージャーの初期化に失敗: \(error)")
}