AwesomeCache

SwiftiOSCache LibraryMobile DevelopmentDisk Cache

Library

AwesomeCache

Overview

AwesomeCache is a high-performance disk cache library for iOS written in Swift.

Details

AwesomeCache is a lightweight and performant cache library written in Swift, developed by iOS developer Alexander Schuch. It supports both memory and disk caching, providing memory caching through NSCache and persistent disk caching through the file system. Individual expiry dates can be set for each cache object with automatic expiration management. It adopts a synchronous API to make reasoning about cache contents easier, while internally using concurrent dispatch queues to achieve thread-safe reads and writes. It also supports caching of asynchronous blocks, allowing efficient caching of API responses and heavy computation results. It supports multiple installation methods including CocoaPods, Carthage, and manual installation, and is widely used as a standard caching solution in iOS development.

Pros and Cons

Pros

  • Sync API: Simple and understandable clean interface
  • Disk Persistence: Data retained after app restart
  • Individual Expiry: Flexible expiration settings per object
  • Thread-Safe: Safety against concurrent access
  • Lightweight: Minimal dependencies and small footprint
  • Swift Optimized: Design leveraging Swift language characteristics
  • Async Support: Support for caching heavy processing

Cons

  • iOS Only: Cannot be used on platforms other than iOS
  • Sync API Limitations: Potential UI blocking with large data processing
  • Disk Usage: Increased storage usage due to persistence
  • Configuration Options: Limited configuration items compared to other libraries
  • Community: Relatively small developer community

Key Links

Code Examples

Installation Methods

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

Basic Cache Operations

import AwesomeCache

// Cache initialization
do {
    let cache = try Cache<NSString>(name: "userCache")
    
    // Store data
    cache["username"] = "john_doe"
    cache["email"] = "[email protected]"
    
    // Retrieve data
    if let username = cache["username"] {
        print("Username: \(username)")
    }
    
    // Delete data
    cache["username"] = nil
    
} catch {
    print("Failed to initialize cache: \(error)")
}

Cache with Expiry

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 Response Caching

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)
        // Data decoding process
        self.data = [:]
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(timestamp, forKey: .timestamp)
        // Data encoding process
    }
}

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("Failed to cache API response: \(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("Failed to decode from cache: \(error)")
            return nil
        }
    }
}

Image Cache Implementation

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")
        
        // Memory cache configuration
        memoryCache.countLimit = 100
        memoryCache.totalCostLimit = 50 * 1024 * 1024 // 50MB
    }
    
    func setImage(_ image: UIImage, forKey key: String, expiryTime: TimeInterval = 86400) {
        // Save to memory cache
        memoryCache.setObject(image, forKey: key as NSString)
        
        // Save to disk cache
        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? {
        // Check memory cache first
        if let image = memoryCache.object(forKey: key as NSString) {
            return image
        }
        
        // Retrieve from disk cache
        if let data = diskCache.object(forKey: key) as Data?,
           let image = UIImage(data: data) {
            // Save to memory cache too
            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()
    }
}

Combination with Async Processing

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
        
        // Check cache first
        if let cachedData = cache.object(forKey: cacheKey) as Data? {
            completion(.success(cachedData))
            return
        }
        
        // Fetch from network
        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
            }
            
            // Save to cache
            let nsData = data as NSData
            self?.cache.setObject(nsData, forKey: cacheKey, expires: .date(Date().addingTimeInterval(3600)))
            
            completion(.success(data))
        }.resume()
    }
}

Cache Statistics and Monitoring

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 {
        // Get number of objects in cache
        // AwesomeCache doesn't have direct count functionality, implementation needed
        return 0
    }
    
    private func getDiskSize<T>(cache: Cache<T>) -> Int64 {
        // Calculate disk usage
        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("Failed to get file size: \(error)")
                }
            }
        }
        
        return size
    }
    
    private func getLastAccessTime<T>(cache: Cache<T>) -> Date? {
        // Get last access time
        // Implementation needed
        return nil
    }
    
    func clearAllCaches() {
        for (_, cacheObj) in caches {
            if let cache = cacheObj as? Cache<NSData> {
                cache.removeAllObjects()
            }
        }
    }
}

// Usage example
do {
    let cacheManager = CacheManager()
    let userCache = try cacheManager.createCache(name: "users", type: NSData.self)
    let imageCache = try cacheManager.createCache(name: "images", type: NSData.self)
    
    // Display cache information
    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("Failed to initialize cache manager: \(error)")
}