-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add Slice * Update documentation and add Constant * Add extra test for Slice * Rename MutableCachedApplicationValue * Add Constant to README Property Wrappers
- Loading branch information
Showing
11 changed files
with
422 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
Sources/AppState/Application/Types/Application+Slice.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
extension Application { | ||
/// `Slice` allows access and modification to a specific part of an AppState's state. Supports `State`, `SyncState`, and `StorageState`. | ||
public struct Slice< | ||
SlicedState: MutableApplicationState, | ||
Value, | ||
SliceValue, | ||
SliceKeyPath: KeyPath<Value, SliceValue> | ||
> where SlicedState.Value == Value { | ||
/// A private backing storage for the value. | ||
private var state: SlicedState | ||
private let keyPath: SliceKeyPath | ||
|
||
init( | ||
_ stateKeyPath: KeyPath<Application, SlicedState>, | ||
value valueKeyPath: SliceKeyPath | ||
) { | ||
self.state = shared.value(keyPath: stateKeyPath) | ||
self.keyPath = valueKeyPath | ||
} | ||
} | ||
} | ||
|
||
extension Application.Slice where SliceKeyPath == KeyPath<Value, SliceValue> { | ||
/// The current state value. | ||
public var value: SliceValue { | ||
state.value[keyPath: keyPath] | ||
} | ||
} | ||
|
||
extension Application.Slice where SliceKeyPath == WritableKeyPath<Value, SliceValue> { | ||
/// The current state value. | ||
public var value: SliceValue { | ||
get { state.value[keyPath: keyPath] } | ||
set { state.value[keyPath: keyPath] = newValue } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
Sources/AppState/Application/Types/MutableApplicationState.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
public protocol MutableApplicationState { | ||
associatedtype Value | ||
|
||
var value: Value { get set } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import Combine | ||
import SwiftUI | ||
|
||
/// A property wrapper that provides access to a specific part of the AppState's state. | ||
@propertyWrapper public struct Constant<SlicedState: MutableApplicationState, Value, SliceValue> where SlicedState.Value == Value { | ||
/// Holds the singleton instance of `Application`. | ||
@ObservedObject private var app: Application = Application.shared | ||
|
||
/// Path for accessing `State` from Application. | ||
private let stateKeyPath: KeyPath<Application, SlicedState> | ||
|
||
/// Path for accessing `SliceValue` from `Value`. | ||
private let valueKeyPath: KeyPath<Value, SliceValue> | ||
|
||
private let fileID: StaticString | ||
private let function: StaticString | ||
private let line: Int | ||
private let column: Int | ||
private let sliceKeyPath: String | ||
|
||
/// Represents the current value of the `State`. | ||
public var wrappedValue: SliceValue { | ||
Application.slice( | ||
stateKeyPath, | ||
valueKeyPath, | ||
fileID, | ||
function, | ||
line, | ||
column | ||
).value | ||
} | ||
|
||
/** | ||
Initializes a Constant with the provided parameters. This constructor is used to create a Constant that provides access to a specific part of an AppState's state. It provides granular control over the AppState. | ||
|
||
- Parameters: | ||
- stateKeyPath: A KeyPath that points to the state in AppState that should be sliced. | ||
- valueKeyPath: A KeyPath that points to the specific part of the state that should be accessed. | ||
*/ | ||
public init( | ||
_ stateKeyPath: KeyPath<Application, SlicedState>, | ||
_ valueKeyPath: KeyPath<Value, SliceValue>, | ||
_ fileID: StaticString = #fileID, | ||
_ function: StaticString = #function, | ||
_ line: Int = #line, | ||
_ column: Int = #column | ||
) { | ||
self.stateKeyPath = stateKeyPath | ||
self.valueKeyPath = valueKeyPath | ||
self.fileID = fileID | ||
self.function = function | ||
self.line = line | ||
self.column = column | ||
|
||
let stateKeyPathString = String(describing: stateKeyPath) | ||
let valueTypeCharacterCount = String(describing: Value.self).count | ||
var valueKeyPathString = String(describing: valueKeyPath) | ||
|
||
valueKeyPathString.removeFirst(valueTypeCharacterCount + 1) | ||
|
||
self.sliceKeyPath = "\(stateKeyPathString)\(valueKeyPathString)" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import Combine | ||
import SwiftUI | ||
|
||
/// A property wrapper that provides access to a specific part of the AppState's state. | ||
@propertyWrapper public struct Slice<SlicedState: MutableApplicationState, Value, SliceValue>: DynamicProperty where SlicedState.Value == Value { | ||
/// Holds the singleton instance of `Application`. | ||
@ObservedObject private var app: Application = Application.shared | ||
|
||
/// Path for accessing `State` from Application. | ||
private let stateKeyPath: KeyPath<Application, SlicedState> | ||
|
||
/// Path for accessing `SliceValue` from `Value`. | ||
private let valueKeyPath: WritableKeyPath<Value, SliceValue> | ||
|
||
private let fileID: StaticString | ||
private let function: StaticString | ||
private let line: Int | ||
private let column: Int | ||
private let sliceKeyPath: String | ||
|
||
/// Represents the current value of the `State`. | ||
public var wrappedValue: SliceValue { | ||
get { | ||
Application.slice( | ||
stateKeyPath, | ||
valueKeyPath, | ||
fileID, | ||
function, | ||
line, | ||
column | ||
).value | ||
} | ||
nonmutating set { | ||
Application.log( | ||
debug: "🍕 Setting Slice \(sliceKeyPath) = \(newValue)", | ||
fileID: fileID, | ||
function: function, | ||
line: line, | ||
column: column | ||
) | ||
|
||
var state = app.value(keyPath: stateKeyPath) | ||
state.value[keyPath: valueKeyPath] = newValue | ||
} | ||
} | ||
|
||
/// A binding to the `State`'s value, which can be used with SwiftUI views. | ||
public var projectedValue: Binding<SliceValue> { | ||
Binding( | ||
get: { wrappedValue }, | ||
set: { wrappedValue = $0 } | ||
) | ||
} | ||
|
||
/** | ||
Initializes a Slice with the provided parameters. This constructor is used to create a Slice that provides access and modification to a specific part of an AppState's state. It provides granular control over the AppState. | ||
|
||
- Parameters: | ||
- stateKeyPath: A KeyPath that points to the state in AppState that should be sliced. | ||
- valueKeyPath: A WritableKeyPath that points to the specific part of the state that should be accessed. | ||
*/ | ||
public init( | ||
_ stateKeyPath: KeyPath<Application, SlicedState>, | ||
_ valueKeyPath: WritableKeyPath<Value, SliceValue>, | ||
_ fileID: StaticString = #fileID, | ||
_ function: StaticString = #function, | ||
_ line: Int = #line, | ||
_ column: Int = #column | ||
) { | ||
self.stateKeyPath = stateKeyPath | ||
self.valueKeyPath = valueKeyPath | ||
self.fileID = fileID | ||
self.function = function | ||
self.line = line | ||
self.column = column | ||
|
||
let stateKeyPathString = String(describing: stateKeyPath) | ||
let valueTypeCharacterCount = String(describing: Value.self).count | ||
var valueKeyPathString = String(describing: valueKeyPath) | ||
|
||
valueKeyPathString.removeFirst(valueTypeCharacterCount + 1) | ||
|
||
self.sliceKeyPath = "\(stateKeyPathString)\(valueKeyPathString)" | ||
} | ||
|
||
/// A property wrapper's synthetic storage property. This is just for SwiftUI to mutate the `wrappedValue` and send event through `objectWillChange` publisher when the `wrappedValue` changes | ||
public static subscript<OuterSelf: ObservableObject>( | ||
_enclosingInstance observed: OuterSelf, | ||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<OuterSelf, SliceValue>, | ||
storage storageKeyPath: ReferenceWritableKeyPath<OuterSelf, Self> | ||
) -> SliceValue { | ||
get { | ||
observed[keyPath: storageKeyPath].wrappedValue | ||
} | ||
set { | ||
guard | ||
let publisher = observed.objectWillChange as? ObservableObjectPublisher | ||
else { return } | ||
|
||
publisher.send() | ||
observed[keyPath: storageKeyPath].wrappedValue = newValue | ||
} | ||
} | ||
} |
Oops, something went wrong.