We can have different types of storages (UserDefaults, NSCache, Keychain etc) for storing some simple data by key. But each of these stores has its own API.
This is the moment when PersistedStorage and PersistedValue can help. Caller side of this protocol knows nothing about the type of storage as the API will be the same. It also solved a problem with persisted type and you can store any type you want without changing API.
Usage
PersistedValue has a number of operators that can simplify usage. Here an example some of them:
let persistedValue = storage.persistedValue(forKey: "key")
// store Codable types
.codable(Model.self)
// provides default value
.default(Model())
// print setters and getter in console
.print()
// reads current value
let model = persistedValue.wrappedValue
// writes a new value
persistedValue.wrappedValue = Model()
Highly recommend wrapping persisted values in separate service. It’ll be single source of truth and can be easily injected.
protocol PersistedValuesServiceProtocol {
var userToggle: AnyPersistedValue<Bool> { get }
var userId: AnyPersistedValue<String?> { get }
var token: PersistedValues.Subject<Token?> { get }
}
final class PersistedValuesService: PersistedValuesServiceProtocol {
let userToggle: AnyPersistedValue<Bool>
let userId: AnyPersistedValue<String?>
let token: PersistedValues.Subject<Token?>
init(
keychain: PersistedStorage,
userDefaults: PersistedStorage
) {
self.userToggle = userDefaults.persistedValue(forKey: "user_toogle")
.bool()
.default(false)
.eraseToAnyPersistedValue()
self.userId = keychain.persistedValue(forKey: "user_id")
.string()
.eraseToAnyPersistedValue()
self.token = keychain.persistedValue(forKey: "token")
.codable(Token.self)
.subject(didChage: keychain.didChange(forKey: "token"))
}
}
struct Token: Codable {
let access: String
}
struct SomeViewModel {
private let persistedValues: PersistedValuesServiceProtocol
init(persistedValues: PersistedValuesServiceProtocol) {
self.persistedValues = persistedValues
}
func logOut() {
self.persistedValues.userId.wrappedValue = nil
}
}
AnyPersistedValue
The AnyPersistedValue provides a type-erasing wrapper for the PersistedValue protocol, that helps to avoid introducing generics in your code in some cases like this:
class SomeViewModel<PV> where PV: PersistedValue, Value == String? {
private let userId: PV
init(userId: PV) {
self.userId = userId
}
func logOut() {
self.userId.wrappedValue = nil
}
}
This generic SomeViewModel<PersistedValue> will be hard to have as a property and you don’t get benefits by this generic model. So it can be refactored with AnyPersistedValue in this way:
class SomeViewModel {
private let userId: AnyPersistedValue<String?>
init<PV>(userId: PV) where PV: PersistedValue, Value == String? {
self.userId = userId.eraseToAnyPersistedValue()
}
func logOut() {
self.userId.wrappedValue = nil
}
}
Mocks
For testing proposals you can use MockPersistedStorage and MockPersistedValue from PersistedValueTestingUtilities target.
A simple way to have persisted data in different storages (like Keychain or UserDefaults) that can be easily extended or tested.
AnyPersistedValue
Mocks
Motivation
We can have different types of storages (UserDefaults, NSCache, Keychain etc) for storing some simple data by key. But each of these stores has its own API.
This is the moment when
PersistedStorage
andPersistedValue
can help. Caller side of this protocol knows nothing about the type of storage as the API will be the same. It also solved a problem with persisted type and you can store any type you want without changing API.Usage
PersistedValue
has a number of operators that can simplify usage. Here an example some of them:Highly recommend wrapping persisted values in separate service. It’ll be single source of truth and can be easily injected.
AnyPersistedValue
The
AnyPersistedValue
provides a type-erasing wrapper for thePersistedValue
protocol, that helps to avoid introducing generics in your code in some cases like this:This generic
SomeViewModel<PersistedValue>
will be hard to have as a property and you don’t get benefits by this generic model. So it can be refactored withAnyPersistedValue
in this way:Mocks
For testing proposals you can use
MockPersistedStorage
andMockPersistedValue
fromPersistedValueTestingUtilities
target.Requirements
Installation
You can add PersistedValue to an Xcode project by adding it as a package dependency.
Alternatives