CodableCache

SwiftiOSキャッシュライブラリCodableメモリキャッシュディスクキャッシュNSCache

キャッシュライブラリ

CodableCache

概要

CodableCacheは、Swift 4のCodableプロトコルを使用したiOS向けのメモリ・ディスクキャッシュライブラリです。

詳細

CodableCache(コーダブルキャッシュ)は、NSCache、NSKeyedArchive、そしてSwift 4のCodableプロトコルをベースにした、シンプルで信頼性の高いキャッシュライブラリです。Adam Sowersによって開発されたこのライブラリは、プレーンなSwift構造体のシームレスなメモリキャッシュとディスク永続化を可能にします。従来のNSCodingベースのフレームワークの代替として設計されており、モデルを定義してCodableに準拠させるだけで使用を開始できます。メモリストレージにはNSCacheを使用し、システムがメモリ不足になった際の自動削除機能を提供します。ディスクストレージには.cachesDirectoryや.applicationSupportDirectoryなどを指定でき、アプリケーション設定やユーザーデータの永続化に適しています。CoreData、Realm、SQLiteのような重厚なソリューションとは異なり、JSON APIに基づくローカル状態のバックアップに最適化されており、迅速なモデル定義、ボイラープレートコードの削減、高速なデータ保存を実現します。

メリット・デメリット

メリット

  • Codable対応: Swift 4のCodableプロトコルとの完全統合
  • 二重ストレージ: メモリとディスクの両方でキャッシュ
  • NSCache活用: システムメモリ管理による自動削除
  • 簡単導入: CocoaPods、Carthage、Swift Package Managerに対応
  • 型安全性: Swiftの型システムによる安全なキャッシュ操作
  • カスタムディレクトリ: 永続化先ディレクトリの指定可能
  • 軽量設計: 必要最小限の機能に絞ったシンプルな実装

デメリット

  • iOS限定: SwiftとiOS環境でのみ利用可能
  • 機能限定: 複雑なクエリや関係性管理には不向き
  • キャッシュ戦略: LRUなどの高度なキャッシュアルゴリズムは未実装
  • 並行性: マルチスレッド環境での同期機能が限定的
  • サイズ制限: キャッシュサイズの詳細制御が困難

主要リンク

書き方の例

基本的なセットアップ

import CodableCache

struct Person: Codable {
    let name: String
    let age: Double // 子供の半年齢も表現可能
}

final class PersonManager {
    let cache: CodableCache<Person>
    
    init(cacheKey: AnyHashable) {
        cache = CodableCache<Person>(key: cacheKey)
    }
    
    func savePerson(_ person: Person) {
        do {
            try cache.set(value: person)
            print("Person saved successfully")
        } catch {
            print("Failed to save person: \(error)")
        }
    }
    
    func loadPerson() -> Person? {
        return cache.get()
    }
}

JSONデータのキャッシュ

import CodableCache
import Foundation

struct APIResponse: Codable {
    let users: [User]
    let totalCount: Int
}

struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

class APICache {
    private let cache = CodableCache<APIResponse>(key: "api_response")
    
    func cacheAPIResponse(_ response: APIResponse) {
        do {
            try cache.set(value: response)
        } catch {
            print("キャッシュの保存に失敗: \(error)")
        }
    }
    
    func getCachedResponse() -> APIResponse? {
        return cache.get()
    }
    
    func clearCache() {
        cache.removeValue()
    }
}

アプリ設定の永続化

import CodableCache

struct AppSettings: Codable {
    var theme: String = "light"
    var notifications: Bool = true
    var language: String = "ja"
    var cacheSize: Int = 100
}

class SettingsManager {
    private let settingsCache = CodableCache<AppSettings>(
        key: "app_settings"
    )
    
    func loadSettings() -> AppSettings {
        return settingsCache.get() ?? AppSettings()
    }
    
    func saveSettings(_ settings: AppSettings) {
        do {
            try settingsCache.set(value: settings)
        } catch {
            print("設定の保存に失敗: \(error)")
        }
    }
    
    func updateTheme(_ theme: String) {
        var settings = loadSettings()
        settings.theme = theme
        saveSettings(settings)
    }
}

// 使用例
let manager = SettingsManager()
var settings = manager.loadSettings()
settings.theme = "dark"
manager.saveSettings(settings)

カスタムディレクトリでの永続化

import CodableCache

struct UserProfile: Codable {
    let userId: String
    let displayName: String
    let avatarURL: String?
    let lastLoginDate: Date
}

class UserProfileCache {
    // システムによって削除されない永続ストレージ
    private let persistentCache = CodableCache<UserProfile>(
        key: "user_profile",
        directory: .applicationSupportDirectory
    )
    
    // 一時的なキャッシュ(システムが削除可能)
    private let temporaryCache = CodableCache<[String]>(
        key: "recent_searches",
        directory: .cachesDirectory
    )
    
    func saveUserProfile(_ profile: UserProfile) {
        do {
            try persistentCache.set(value: profile)
            print("ユーザープロフィールを永続化しました")
        } catch {
            print("プロフィール保存エラー: \(error)")
        }
    }
    
    func loadUserProfile() -> UserProfile? {
        return persistentCache.get()
    }
    
    func saveRecentSearches(_ searches: [String]) {
        do {
            try temporaryCache.set(value: searches)
        } catch {
            print("検索履歴保存エラー: \(error)")
        }
    }
}

複数のキャッシュ管理

import CodableCache

class DataManager {
    private let userCache = CodableCache<User>(key: "current_user")
    private let postsCache = CodableCache<[Post]>(key: "user_posts")
    private let configCache = CodableCache<AppConfig>(key: "app_config")
    
    // ユーザー情報の管理
    func cacheCurrentUser(_ user: User) {
        try? userCache.set(value: user)
    }
    
    func getCurrentUser() -> User? {
        return userCache.get()
    }
    
    // 投稿データの管理
    func cachePosts(_ posts: [Post]) {
        try? postsCache.set(value: posts)
    }
    
    func getCachedPosts() -> [Post] {
        return postsCache.get() ?? []
    }
    
    // 設定データの管理
    func cacheConfig(_ config: AppConfig) {
        try? configCache.set(value: config)
    }
    
    func getConfig() -> AppConfig? {
        return configCache.get()
    }
    
    // 全キャッシュのクリア
    func clearAllCaches() {
        userCache.removeValue()
        postsCache.removeValue()
        configCache.removeValue()
    }
}

ネットワークレスポンスキャッシュの実践例

import CodableCache
import Foundation

struct NewsArticle: Codable {
    let id: String
    let title: String
    let content: String
    let publishedAt: Date
    let author: String
}

class NewsCache {
    private let articlesCache = CodableCache<[NewsArticle]>(
        key: "latest_articles"
    )
    
    private let lastUpdateCache = CodableCache<Date>(
        key: "articles_last_update"
    )
    
    func cacheArticles(_ articles: [NewsArticle]) {
        do {
            try articlesCache.set(value: articles)
            try lastUpdateCache.set(value: Date())
            print("\(articles.count)件の記事をキャッシュしました")
        } catch {
            print("記事キャッシュエラー: \(error)")
        }
    }
    
    func getCachedArticles() -> [NewsArticle] {
        return articlesCache.get() ?? []
    }
    
    func isCacheExpired(maxAge: TimeInterval = 3600) -> Bool {
        guard let lastUpdate = lastUpdateCache.get() else {
            return true
        }
        return Date().timeIntervalSince(lastUpdate) > maxAge
    }
    
    func getCachedArticlesIfValid() -> [NewsArticle]? {
        guard !isCacheExpired() else {
            return nil
        }
        return articlesCache.get()
    }
}