AwesomeCache
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
- AwesomeCache GitHub
- AwesomeCache CocoaPods
- Swift Caching Article
- Little Bites of Cocoa - AwesomeCache
- iOS Performance Guide
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)")
}