Files
purchases-ios/Sources/Networking/GenerationGuardedCache.swift
T
Rick 67c3140f2b fix(remote-config): Cache remote config workflows in-memory to avoid loading flash (#7213)
* Cache remote config workflows for first frame

* Warm prefetched workflow assets before first frame

* Avoid blocking workflow warmup on assets

* Fix stale remote config cache reads

* Skip redundant prefetched workflow warmup

* Test prefetched workflow warmup short-circuit

* Address remote config workflow review feedback

* Skip workflow asset warm test before iOS 15

* Fix FileRepository test for Swift 6

* Retry incomplete workflow cache warmup
2026-07-16 18:07:14 +02:00

79 lines
2.3 KiB
Swift

//
// GenerationGuardedCache.swift
// RevenueCat
//
// Created by Rick van der Linden.
// Copyright © 2026 RevenueCat, Inc. All rights reserved.
import Foundation
/// Small in-memory cache for remote-config-derived values.
///
/// Values are tagged with the remote config generation and a provider-owned key. Reads only return when
/// the generation is still current, and stale lower-generation stores cannot overwrite newer cache state.
final class GenerationGuardedCache<Key: Equatable, Value> {
private let lock = Lock()
private var cached: CachedValue?
func value(currentGeneration: Int) -> Value? {
return self.lock.perform {
guard let cached = self.cached else { return nil }
guard cached.generation == currentGeneration else {
if cached.generation < currentGeneration {
self.cached = nil
}
return nil
}
return cached.value
}
}
func value(for snapshot: GenerationGuardedCacheSnapshot<Key>) -> Value? {
return self.lock.perform {
guard let cached = self.cached else { return nil }
guard cached.generation == snapshot.generation,
cached.key == snapshot.key else {
if cached.generation <= snapshot.generation {
self.cached = nil
}
return nil
}
return cached.value
}
}
func store(_ value: Value, for snapshot: GenerationGuardedCacheSnapshot<Key>) {
self.lock.perform {
guard snapshot.generation >= (self.cached?.generation ?? Int.min) else { return }
self.cached = .init(generation: snapshot.generation, key: snapshot.key, value: value)
}
}
func clearIfStale(currentGeneration: Int) {
self.lock.perform {
guard let cached = self.cached,
cached.generation < currentGeneration else { return }
self.cached = nil
}
}
}
struct GenerationGuardedCacheSnapshot<Key: Equatable> {
let generation: Int
let key: Key
}
private extension GenerationGuardedCache {
struct CachedValue {
let generation: Int
let key: Key
let value: Value
}
}
extension GenerationGuardedCache: @unchecked Sendable {}