123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- import Foundation
- public enum MemoryStorage {
-
-
-
-
-
-
-
-
-
-
- public class Backend<T: CacheCostCalculable> {
- let storage = NSCache<NSString, StorageObject<T>>()
-
-
-
-
-
-
- var keys = Set<String>()
- private var cleanTimer: Timer? = nil
- private let lock = NSLock()
-
-
- public var config: Config {
- didSet {
- storage.totalCostLimit = config.totalCostLimit
- storage.countLimit = config.countLimit
- }
- }
-
-
-
-
- public init(config: Config) {
- self.config = config
- storage.totalCostLimit = config.totalCostLimit
- storage.countLimit = config.countLimit
- cleanTimer = .scheduledTimer(withTimeInterval: config.cleanInterval, repeats: true) { [weak self] _ in
- guard let self = self else { return }
- self.removeExpired()
- }
- }
-
- public func removeExpired() {
- lock.lock()
- defer { lock.unlock() }
- for key in keys {
- let nsKey = key as NSString
- guard let object = storage.object(forKey: nsKey) else {
-
-
-
- keys.remove(key)
- continue
- }
- if object.estimatedExpiration.isPast {
- storage.removeObject(forKey: nsKey)
- keys.remove(key)
- }
- }
- }
-
-
-
-
-
-
- public func store(
- value: T,
- forKey key: String,
- expiration: StorageExpiration? = nil)
- {
- storeNoThrow(value: value, forKey: key, expiration: expiration)
- }
-
-
- func storeNoThrow(
- value: T,
- forKey key: String,
- expiration: StorageExpiration? = nil)
- {
- lock.lock()
- defer { lock.unlock() }
- let expiration = expiration ?? config.expiration
-
- guard !expiration.isExpired else { return }
-
- let object = StorageObject(value, key: key, expiration: expiration)
- storage.setObject(object, forKey: key as NSString, cost: value.cacheCost)
- keys.insert(key)
- }
-
-
-
-
-
-
-
- public func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) -> T? {
- guard let object = storage.object(forKey: key as NSString) else {
- return nil
- }
- if object.expired {
- return nil
- }
- object.extendExpiration(extendingExpiration)
- return object.value
- }
-
-
-
- public func isCached(forKey key: String) -> Bool {
- guard let _ = value(forKey: key, extendingExpiration: .none) else {
- return false
- }
- return true
- }
-
-
- public func remove(forKey key: String) {
- lock.lock()
- defer { lock.unlock() }
- storage.removeObject(forKey: key as NSString)
- keys.remove(key)
- }
-
- public func removeAll() {
- lock.lock()
- defer { lock.unlock() }
- storage.removeAllObjects()
- keys.removeAll()
- }
- }
- }
- extension MemoryStorage {
-
- public struct Config {
-
- public var totalCostLimit: Int
-
- public var countLimit: Int = .max
-
-
- public var expiration: StorageExpiration = .seconds(300)
-
- public let cleanInterval: TimeInterval
-
-
-
-
-
-
-
-
-
- public init(totalCostLimit: Int, cleanInterval: TimeInterval = 120) {
- self.totalCostLimit = totalCostLimit
- self.cleanInterval = cleanInterval
- }
- }
- }
- extension MemoryStorage {
- class StorageObject<T> {
- let value: T
- let expiration: StorageExpiration
- let key: String
-
- private(set) var estimatedExpiration: Date
-
- init(_ value: T, key: String, expiration: StorageExpiration) {
- self.value = value
- self.key = key
- self.expiration = expiration
-
- self.estimatedExpiration = expiration.estimatedExpirationSinceNow
- }
- func extendExpiration(_ extendingExpiration: ExpirationExtending = .cacheTime) {
- switch extendingExpiration {
- case .none:
- return
- case .cacheTime:
- self.estimatedExpiration = expiration.estimatedExpirationSinceNow
- case .expirationTime(let expirationTime):
- self.estimatedExpiration = expirationTime.estimatedExpirationSinceNow
- }
- }
-
- var expired: Bool {
- return estimatedExpiration.isPast
- }
- }
- }
|