Overview of Cache
masterCodable protocol for seamless serialization of any conforming type.repository·master·Indexed 25 days ago
https://github.com/hyperoslo/cacheA lightweight, thread-safe Swift caching library for iOS, tvOS, and macOS. It utilizes Codable for serialization and supports hybrid memory and disk storage, object expiry, and both synchronous and asynchronous APIs. Features include customizable DiskConfig and MemoryConfig, type-safe Storage instances via transformers, and a token-based observation system for monitoring storage changes.
Codable protocol for seamless serialization of any conforming type.Cache provides the following capabilities:
Codable for easy saving and loading.DiskConfig and MemoryConfig.expiry and automatic cleanup of expired items.UIImageView or NSImageView, use Imaginary. It uses Cache internally to handle remote image caching efficiently.To install or update Cache using CocoaPods, add the following line to your Podfile:
pod 'Cache', :git => 'https://github.com/hyperoslo/Cache.git'To create a Storage instance, provide DiskConfig and MemoryConfig. The Storage class uses a HybridStorage approach by default, combining fast in-memory access with persistent disk storage. You can also provide a transformer using TransformerFactory to handle type serialization (e.g., for Codable types).
let diskConfig = DiskConfig(name: "Floppy")
let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)
let storage = try? Storage(
diskConfig: diskConfig,
memoryConfig: memoryConfig,
transformer: TransformerFactory.forCodable(ofType: User.self) // Storage<String, User>
)To install Cache using Carthage, add the following line to your Cartfile:
github "hyperoslo/Cache"Note: You must also add SwiftHash.framework to your copy-frameworks script.
When fetching JSON from a backend (e.g., using Alamofire), it is recommended to decode the JSON into strongly typed objects before saving them to Storage. Storage can persist String or Data, or you can use JSONArrayWrapper and JSONDictionaryWrapper for JSON. However, persisting strongly typed objects is preferred for UI consistency.
Use the JSONDecoder extensions to decode String, Dictionary, or Data into your models:
let user = JSONDecoder.decode(jsonString, to: User.self)
let cities = JSONDecoder.decode(jsonDictionary, to: [City].self)
let dragons = JSONDecoder.decode(jsonData, to: [Dragon].self)Example workflow with Alamofire:
Alamofire.request("https://gameofthrones.org/mostFavoriteCharacter").responseString { response in
do {
let user = try JSONDecoder.decode(response.result.success, to: User.self)
try storage.setObject(user, forKey: "most favorite character")
} catch {
print(error)
}
}Use DiskConfig to customize disk persistence:
name: The folder name within the directory.expiry: Default expiry date for added objects.maxSize: Maximum size of the disk cache in bytes.directory: Custom URL for storage (defaults to cachesDirectory).protectionType: iOS/tvOS specific FileProtectionType for encryption.let diskConfig = DiskConfig(
name: "Floppy",
expiry: .date(Date().addingTimeInterval(2*3600)),
maxSize: 10000,
directory: try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask,
appropriateFor: nil, create: true).appendingPathComponent("MyPreferences"),
protectionType: .complete
)Use MemoryConfig to customize in-memory caching:
expiry: Default expiry date for added objects.countLimit: Maximum number of objects allowed in memory.totalCostLimit: Maximum total cost (e.g., bytes/size) before eviction starts.let memoryConfig = MemoryConfig(
expiry: .date(Date().addingTimeInterval(2*60)),
countLimit: 50,
totalCostLimit: 0
)Synchronous APIs are thread-safe and can be accessed from any queue. Use try? or do-catch to handle potential StorageErrors.
// Save to storage
try? storage.setObject(10, forKey: "score")
try? storage.setObject("Oslo", forKey: "my favorite city", expiry: .never)
// Load from storage
let score = try? storage.object(forKey: "score")
// Check existence
let hasFavoriteCharacter = try? storage.objectExists(forKey: "my favorite city")
// Remove items
try? storage.removeObject(forKey: "my favorite city")
try? storage.removeAll()
try? storage.removeExpiredObjects()
// Get entry with metadata
let entry = try? storage.entry(forKey: "my favorite city")
print(entry?.object)
print(entry?.expiry)
print(entry?.meta)All Storage instances are generic and type-safe. Once created, the type constraint is maintained for all subsequent operations. You can create new Storage instances with different type annotations from an existing one using transformation methods like transformImage() or transformCodable(ofType:). This allows you to work with different types while sharing the same underlying caching mechanism.
let storage: Storage<String, User> = ...
storage.setObject(superman, forKey: "user")
let imageStorage = storage.transformImage() // Storage<String, UIImage>
imageStorage.setObject(image, forKey: "image")
let stringStorage = storage.transformCodable(ofType: String.self) // Storage<String, String>
stringStorage.setObject("hello world", forKey: "string")Access asynchronous APIs via the .async property. These return a Result type in a completion block or support Swift Concurrency (async/await).
// Completion handler pattern
storage.async.setObject("Oslo", forKey: "my favorite city") { result in
switch result {
case .success: print("saved successfully")
case .failure(let error): print(error)
}
}
// Swift Concurrency pattern
do {
try await storage.async.setObject("Oslo", forKey: "my favorite city")
print("saved successfully")
} catch {
print(error)
}