-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Cache configuration for a given ClientSession #959
Changes from all commits
9d11732
29d48dc
b29480d
daae31c
0e7114c
da839d8
f9d54db
21a438e
5d3c7e4
f4e130e
64f86ce
661f8de
25220ff
228c930
3558a8c
9787b5f
4b75f7b
cf0b551
f9915e2
06048cb
6ff4a56
e0ad8b4
3ff3058
ca93d3e
5de40a6
6c03ba0
b457de7
5914d17
5e887f9
9e43a6f
4352203
0d4e825
dcc5998
56c8a10
0e20c49
1df039a
68e7282
9663e6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// | ||
// Cache.swift | ||
// PrimerSDK | ||
// | ||
// Created by Niall Quinn on 31/07/24. | ||
// | ||
|
||
import Foundation | ||
|
||
class Cache<Key: Hashable, Value> { | ||
private let cache = NSCache<WrappedKey, Entry>() | ||
|
||
func insert(_ value: Value, forKey key: Key) { | ||
cache.setObject(Entry(value: value), forKey: WrappedKey(key)) | ||
} | ||
|
||
func value(forKey key: Key) -> Value? { | ||
guard let entry = cache.object(forKey: WrappedKey(key)) else { | ||
return nil | ||
} | ||
return entry.value | ||
} | ||
|
||
func removeValue(forKey key: Key) { | ||
cache.removeObject(forKey: WrappedKey(key)) | ||
} | ||
} | ||
|
||
private extension Cache { | ||
final class WrappedKey: NSObject { | ||
let key: Key | ||
init(_ key: Key) { self.key = key } | ||
|
||
override var hash: Int { return key.hashValue } | ||
|
||
override func isEqual(_ object: Any?) -> Bool { | ||
guard let value = object as? WrappedKey else { | ||
return false | ||
} | ||
return value.key == key | ||
} | ||
} | ||
} | ||
|
||
private extension Cache { | ||
final class Entry { | ||
let value: Value | ||
init(value: Value) { self.value = value } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// | ||
// ConfigurationCache.swift | ||
// PrimerSDK | ||
// | ||
// Created by Niall Quinn on 31/07/24. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol ConfigurationCaching { | ||
func clearCache() | ||
func setData(_ data: ConfigurationCachedData, forKey key: String) | ||
func data(forKey key: String) -> ConfigurationCachedData? | ||
} | ||
|
||
class ConfigurationCache: ConfigurationCaching { | ||
static let shared = ConfigurationCache() | ||
private var cache = Cache<String, ConfigurationCachedData>() | ||
|
||
func clearCache() { | ||
cache = Cache<String, ConfigurationCachedData>() | ||
} | ||
|
||
func data(forKey key: String) -> ConfigurationCachedData? { | ||
guard cachingEnabled else { | ||
return nil | ||
} | ||
if let cachedData = cache.value(forKey: key) { | ||
if validateCachedConfig(key: key, cachedData: cachedData) == false { | ||
cache.removeValue(forKey: key) | ||
return nil | ||
} | ||
return cachedData | ||
} | ||
return nil | ||
} | ||
|
||
func setData(_ data: ConfigurationCachedData, forKey key: String) { | ||
guard cachingEnabled else { | ||
return | ||
} | ||
// Cache includes at most one cached configuration | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a potential case for having a My concern would be someone seeing this and thinking that it supports multiple configurations because of the provision of a |
||
clearCache() | ||
cache.insert(data, forKey: key) | ||
} | ||
|
||
private func validateCachedConfig(key: String, cachedData: ConfigurationCachedData) -> Bool { | ||
let timestamp = cachedData.timestamp | ||
let now = Date().timeIntervalSince1970 | ||
let timeInterval = now - timestamp | ||
|
||
if timeInterval > cachedData.ttl { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
private var cachingEnabled: Bool { | ||
PrimerSettings.current.clientSessionCachingEnabled | ||
} | ||
} | ||
|
||
class ConfigurationCachedData { | ||
|
||
let config: PrimerAPIConfiguration | ||
let timestamp: TimeInterval | ||
let ttl: TimeInterval | ||
|
||
init(config: PrimerAPIConfiguration, headers: [String: String]? = nil) { | ||
// Extract ttl from headers | ||
self.config = config | ||
self.timestamp = Date().timeIntervalSince1970 | ||
self.ttl = Self.extractTtlFromHeaders(headers) | ||
} | ||
|
||
static let FallbackCacheExpiration: TimeInterval = 0 | ||
static let CacheHeaderKey = "x-primer-session-cache-ttl" | ||
|
||
private static func extractTtlFromHeaders(_ headers: [String: String]?) -> TimeInterval { | ||
guard let headers, | ||
let ttlHeaderValue = headers[Self.CacheHeaderKey], | ||
let ttlInt = Int(ttlHeaderValue) else { | ||
return Self.FallbackCacheExpiration | ||
} | ||
return TimeInterval(ttlInt) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: We have a (spurious ..) extension to decode
[String: Any]
- could use that to keep this symmetric.