Cache (Swift)
Library
Cache (Swift)
Overview
Cache is a modern Swift caching library that supports Swift Package Manager. It provides caching solutions for iOS and macOS supporting both memory and disk caching with time limits and capacity limits.
Details
Cache is a caching library for modern Swift development that's gaining attention due to the widespread adoption of Swift Package Manager. It supports caching on both memory and disk, providing TTL (Time To Live) time limits and capacity limit functionality. Compatible with iOS 9/macOS 10.11 and later, it can cache custom types conforming to the Codable protocol and primitive types (String, Int, etc.). Integration with the Swift Package Manager (SPM) ecosystem allows natural incorporation into modern Swift development workflows, developed by 0xLeif. Compared to other Swift cache libraries like NSCache and HanekeSwift, it features SPM native support and design leveraging modern Swift language features.
Advantages and Disadvantages
Advantages
- Swift Package Manager Support: Complete integration with modern Swift development environment
- Memory & Disk Support: Flexible cache strategy implementation
- Codable Support: Type-safe cache operations and Swift-native serialization
- Time & Capacity Limits: Automatic management with TTL and capacity limits
- iOS/macOS Support: Usage across Apple platforms
- Simple API: Intuitive and easy-to-use interface
- Lightweight Design: Minimal dependencies and memory footprint
Disadvantages
- Apple Platform Only: Cannot be used outside iOS and macOS
- New Library: Less proven track record compared to mature libraries
- Limited Features: Advanced cache features inferior to specialized libraries
- Community Size: Smaller community compared to standard libraries like NSCache
- Documentation: Limited comprehensive documentation
Key Links
- Swift Package Index - Cache
- GitHub Repository
- Swift Package Manager Official
- Codable Protocol Documentation
- NSCache Documentation
- Swift by Sundell - Caching Article
Code Examples
Installation with Swift Package Manager
// Package.swift
import PackageDescription
let package = Package(
name: "MyProject",
dependencies: [
.package(url: "https://github.com/0xLeif/Cache", from: "1.0.0")
],
targets: [
.target(
name: "MyProject",
dependencies: ["Cache"]
)
]
)
Basic Cache Usage
import Cache
// Create Cache instance
let cache = Cache<String, String>()
// Set value
cache.set("user:123", value: "John Doe")
// Get value
if let userName = cache.get("user:123") {
print("User name: \(userName)")
}
// Remove value
cache.remove("user:123")
// Clear cache
cache.clear()
Cache with TTL (Time To Live)
import Cache
// TTL-enabled cache
let cache = Cache<String, String>(defaultTTL: 300) // 5 minutes
// Set value with TTL
cache.set("temporary_data", value: "This will expire", ttl: 60) // Expires in 1 minute
// Check expiration
if cache.isExpired("temporary_data") {
print("Data has expired")
} else {
let data = cache.get("temporary_data")
print("Data: \(data ?? "nil")")
}
Caching Codable Types
import Cache
// Codable-compliant structure
struct User: Codable {
let id: String
let name: String
let email: String
let createdAt: Date
}
// Cache for User objects
let userCache = Cache<String, User>()
// Cache user object
let user = User(
id: "123",
name: "Jane Smith",
email: "[email protected]",
createdAt: Date()
)
userCache.set("user:\(user.id)", value: user, ttl: 3600) // 1 hour
// Retrieve user object
if let cachedUser = userCache.get("user:123") {
print("Cached user: \(cachedUser.name)")
}
Cache Size Limits
import Cache
// Cache with maximum capacity limit
let cache = Cache<String, Data>(maxSize: 100) // Max 100 items
// Check behavior on capacity overflow
for i in 1...150 {
let key = "item:\(i)"
let data = "Data for item \(i)".data(using: .utf8)!
cache.set(key, value: data)
}
print("Cache size: \(cache.count)") // 100 (max capacity)
print("First item exists: \(cache.get("item:1") != nil)") // false (removed)
print("Last item exists: \(cache.get("item:150") != nil)") // true
Asynchronous Cache Operations
import Cache
class AsyncCacheService {
private let cache = Cache<String, Data>()
private let queue = DispatchQueue(label: "cache.queue", attributes: .concurrent)
func setData(_ data: Data, forKey key: String, completion: @escaping () -> Void) {
queue.async(flags: .barrier) {
self.cache.set(key, value: data)
DispatchQueue.main.async {
completion()
}
}
}
func getData(forKey key: String, completion: @escaping (Data?) -> Void) {
queue.async {
let data = self.cache.get(key)
DispatchQueue.main.async {
completion(data)
}
}
}
func clearCache(completion: @escaping () -> Void) {
queue.async(flags: .barrier) {
self.cache.clear()
DispatchQueue.main.async {
completion()
}
}
}
}
// Usage example
let cacheService = AsyncCacheService()
cacheService.setData("Hello, Cache!".data(using: .utf8)!, forKey: "greeting") {
print("Data cached successfully")
}
cacheService.getData(forKey: "greeting") { data in
if let data = data, let string = String(data: data, encoding: .utf8) {
print("Retrieved: \(string)")
}
}
Hybrid Memory-Disk Cache
import Cache
import Foundation
class HybridCache<Key: Hashable & Codable, Value: Codable> {
private let memoryCache = Cache<Key, Value>(maxSize: 50) // Memory max 50 items
private let diskCacheURL: URL
init(diskCacheDirectory: String = "HybridCache") {
let documentsPath = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first!
self.diskCacheURL = documentsPath.appendingPathComponent(diskCacheDirectory)
// Create directory
try? FileManager.default.createDirectory(at: diskCacheURL,
withIntermediateDirectories: true)
}
func set(_ key: Key, value: Value, ttl: TimeInterval? = nil) {
// Save to memory cache
memoryCache.set(key, value: value, ttl: ttl)
// Save to disk cache
saveToDisk(key: key, value: value)
}
func get(_ key: Key) -> Value? {
// Try memory first
if let value = memoryCache.get(key) {
return value
}
// Load from disk and restore to memory
if let value = loadFromDisk(key: key) {
memoryCache.set(key, value: value)
return value
}
return nil
}
private func saveToDisk(key: Key, value: Value) {
do {
let data = try JSONEncoder().encode(value)
let url = diskCacheURL.appendingPathComponent("\(key).cache")
try data.write(to: url)
} catch {
print("Failed to save to disk: \(error)")
}
}
private func loadFromDisk(key: Key) -> Value? {
do {
let url = diskCacheURL.appendingPathComponent("\(key).cache")
let data = try Data(contentsOf: url)
return try JSONDecoder().decode(Value.self, from: data)
} catch {
return nil
}
}
}
// Usage example
let hybridCache = HybridCache<String, User>()
hybridCache.set("user:456", value: User(
id: "456",
name: "Bob Wilson",
email: "[email protected]",
createdAt: Date()
))
if let user = hybridCache.get("user:456") {
print("Retrieved user: \(user.name)")
}
Cache Statistics and Monitoring
import Cache
class MonitoredCache<Key: Hashable, Value> {
private let cache = Cache<Key, Value>()
private var hitCount = 0
private var missCount = 0
private var setCount = 0
var hitRate: Double {
let total = hitCount + missCount
return total > 0 ? Double(hitCount) / Double(total) : 0
}
func set(_ key: Key, value: Value, ttl: TimeInterval? = nil) {
cache.set(key, value: value, ttl: ttl)
setCount += 1
}
func get(_ key: Key) -> Value? {
if let value = cache.get(key) {
hitCount += 1
return value
} else {
missCount += 1
return nil
}
}
func statistics() -> (hits: Int, misses: Int, sets: Int, hitRate: Double) {
return (hitCount, missCount, setCount, hitRate)
}
func resetStatistics() {
hitCount = 0
missCount = 0
setCount = 0
}
}
// Usage example
let monitoredCache = MonitoredCache<String, String>()
monitoredCache.set("key1", value: "value1")
monitoredCache.set("key2", value: "value2")
_ = monitoredCache.get("key1") // Hit
_ = monitoredCache.get("key3") // Miss
let stats = monitoredCache.statistics()
print("Hit rate: \(stats.hitRate)") // 0.5 (50%)