Skip to content

Commit

Permalink
Updated StoredValue with specific types only
Browse files Browse the repository at this point in the history
  • Loading branch information
PimCoumans committed Jan 18, 2024
1 parent f989b94 commit c2d2815
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 32 deletions.
196 changes: 164 additions & 32 deletions Sources/DidUpdate/Property Wrappers/StoredValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public struct StoredValue<Value> {
let getter: () -> Value?
let setter: (Value) -> Void

private var storage: Value {
var storage: Value {
get {
getter() ?? defaultValue
}
Expand All @@ -20,19 +20,19 @@ public struct StoredValue<Value> {
}
}

/// Creates a new StoredValue property wrapper
/// - Parameters:
/// - wrappedValue: Default value when value not found in `UserDefaults`
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) {
defaultValue = wrappedValue
getter = {
store.value(forKey: key) as? Value
}
setter = { value in
store.set(value, forKey: key)
}
@available(
*, unavailable,
message: "This property wrapper can only be applied to properties of classes conforming to ObservableState")
public var wrappedValue: Value {
get { fatalError() }
set { fatalError() }
}

@available(
*, unavailable,
message: "This property wrapper can only be applied to properties of classes conforming to ObservableState")
public var projectedValue: ReadOnlyProxy<Value> {
fatalError()
}

/// Updates the enclosing ``ObservableState``’s ``StateObserver`` whenever the value is changed
Expand Down Expand Up @@ -71,40 +71,172 @@ public struct StoredValue<Value> {
)
}
}
}

@available(
*, unavailable,
message: "This property wrapper can only be applied to properties of classes conforming to ObservableState")
public var wrappedValue: Value {
get { fatalError() }
set { fatalError() }
extension StoredValue {
/// Initializes StoredValue property wrapping using a default value
private init(defaultValue: Value, key: String, store: UserDefaults) {
self.defaultValue = defaultValue
getter = {
store.object(forKey: key) as? Value
}
setter = { value in
store.set(value, forKey: key)
}
}

@available(
*, unavailable,
message: "This property wrapper can only be applied to properties of classes conforming to ObservableState")
public var projectedValue: ReadOnlyProxy<Value> {
fatalError()
/// Creates a new StoredValue property wrapper for a Bool value
/// - Parameters:
/// - wrappedValue: Default value when value not found in `UserDefaults`
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Bool {
self.init(defaultValue: wrappedValue, key: key, store: store)
}
/// Creates a new StoredValue property wrapper for an Int value
/// - Parameters:
/// - wrappedValue: Default value when value not found in `UserDefaults`
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Int {
self.init(defaultValue: wrappedValue, key: key, store: store)
}
/// Creates a new StoredValue property wrapper for a Double value
/// - Parameters:
/// - wrappedValue: Default value when value not found in `UserDefaults`
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Double {
self.init(defaultValue: wrappedValue, key: key, store: store)
}
/// Creates a new StoredValue property wrapper for a String value
/// - Parameters:
/// - wrappedValue: Default value when value not found in `UserDefaults`
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == String {
self.init(defaultValue: wrappedValue, key: key, store: store)
}
/// Creates a new StoredValue property wrapper for a URL value
/// - Parameters:
/// - wrappedValue: Default value when value not found in `UserDefaults`
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == URL {
self.init(defaultValue: wrappedValue, key: key, store: store)
}
/// Creates a new StoredValue property wrapper for a Data value
/// - Parameters:
/// - wrappedValue: Default value when value not found in `UserDefaults`
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Data {
self.init(defaultValue: wrappedValue, key: key, store: store)
}
}

extension StoredValue where Value: ExpressibleByNilLiteral {
/// Creates a new StoredValue property wrapper with a default `nil` value
extension StoredValue {
/// Creates a new StoredValue property wrapper for a Set value, storing the value as an Array in the user defaults store
/// - Parameters:
/// - wrappedValue: Default value when value not found in `UserDefaults`
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init<WrappedValue>(wrappedValue: Optional<WrappedValue> = nil, _ key: String, store: UserDefaults = .standard) where Value == Optional<WrappedValue> {
defaultValue = wrappedValue
public init<Element>(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) where Value == Set<Element> {
self.init(
defaultValue: wrappedValue,
getter: {
guard let array = store.object(forKey: key) as? [Element] else {
return nil
}
return Set(array)
},
setter: { value in
store.set(Array(value), forKey: key)
}
)
}
}

extension StoredValue where Value: ExpressibleByNilLiteral {
/// Initializes StoredValue property wrapping using a default optional value
private init<WrappedValue>(key: String, store: UserDefaults) where Value == Optional<WrappedValue> {
defaultValue = nil
getter = {
store.value(forKey: key) as? Value
store.object(forKey: key) as? Value
}
setter = { value in
if let value {
store.setValue(value, forKey: key)
store.set(value, forKey: key)
} else {
store.removeObject(forKey: key)
}
}
}
/// Creates a new StoredValue property wrapper for an optional Bool value
/// - Parameters:
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(_ key: String, store: UserDefaults = .standard) where Value == Bool? {
self.init(key: key, store: store)
}
/// Creates a new StoredValue property wrapper for an optional Int value
/// - Parameters:
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(_ key: String, store: UserDefaults = .standard) where Value == Int? {
self.init(key: key, store: store)
}
/// Creates a new StoredValue property wrapper for an optional Double value
/// - Parameters:
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(_ key: String, store: UserDefaults = .standard) where Value == Double? {
self.init(key: key, store: store)
}
/// Creates a new StoredValue property wrapper for an optional String value
/// - Parameters:
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(_ key: String, store: UserDefaults = .standard) where Value == String? {
self.init(key: key, store: store)
}
/// Creates a new StoredValue property wrapper for an optional URL value
/// - Parameters:
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(_ key: String, store: UserDefaults = .standard) where Value == URL? {
self.init(key: key, store: store)
}
/// Creates a new StoredValue property wrapper for an optional Data value
/// - Parameters:
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init(_ key: String, store: UserDefaults = .standard) where Value == Data? {
self.init(key: key, store: store)
}
}

extension StoredValue {
/// Creates a new StoredValue property wrapper for an optional Set value, storing the value as an Array in the user defaults store
/// - Parameters:
/// - wrappedValue: Default value when value not found in `UserDefaults`
/// - key: Key to use to access `UserDefaults`
/// - store: `UserDefaults` store to use
public init<Element>(_ key: String, store: UserDefaults = .standard) where Value == Set<Element>? {
self.init(
defaultValue: nil,
getter: {
guard let array = store.object(forKey: key) as? [Element] else {
return nil
}
return Set(array)
},
setter: { value in
if let value {
store.set(Array(value), forKey: key)
} else {
store.removeObject(forKey: key)
}
}
)
}
}
27 changes: 27 additions & 0 deletions Tests/DidUpdateTests/ViewModelStateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ final class ViewModelStateTests: XCTestCase {
@ObservedValue var structProperty = ViewModelProperty() { didSet {
structBoolean.value = true
}}

@StoredValue("StoredProperty") var storedProperty: Bool = false
@StoredValue("OptionalStoredProperty") var optionalStoredProperty: String?
}

class SomeView {
Expand Down Expand Up @@ -254,4 +257,28 @@ final class ViewModelStateTests: XCTestCase {
bool.expect(true, operation: { view.viewModel.optional = .zero })
bool.expect(false, operation: { view.viewModel.frame.size.height = 20 })
}

func testStoredValues() {
let view = SomeView()
let bool = BooleanContainer()

var observer = view.$viewModel.storedProperty.didChange { newValue in
bool.value = true
}
_ = observer

let defaults = UserDefaults.standard
defaults.removeObject(forKey: "StoredProperty")
defaults.removeObject(forKey: "OptionalStoredProperty")

bool.expect(false) { view.viewModel.storedProperty = false }
bool.expect(true) { view.viewModel.storedProperty = true }

observer = view.$viewModel.optionalStoredProperty.didChange { newValue in
bool.value = true
}

bool.expect(false) { view.viewModel.optionalStoredProperty = nil }
bool.expect(true) { view.viewModel.optionalStoredProperty = "someString" }
}
}

0 comments on commit c2d2815

Please sign in to comment.