Skip to content

Commit

Permalink
Improve store implementation (#854)
Browse files Browse the repository at this point in the history
* Improve store implementation

* Update tests

* Remove test store & move bind method to extension
  • Loading branch information
tinder-cfuller authored Oct 2, 2024
1 parent 61587a0 commit 4f774ac
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 317 deletions.
123 changes: 23 additions & 100 deletions Customization/Perception.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,6 @@ public protocol PerceptibleViewStateStore<ViewState>: AnyObject, Perceptible {
associatedtype ViewState: Equatable

var viewState: ViewState { get }

func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T>

func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T>
}

// MARK: - State Store
Expand Down Expand Up @@ -119,20 +109,6 @@ public final class AnyPerceptibleViewStateStore<
) where Base.ViewState == ViewState {
box = PerceptibleViewStateStoreBox(base)
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
box.bind(to: keyPath, onChange: onChange)
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
box.bind(to: keyPath, onChange: onChange)
}
}

@preconcurrency
Expand All @@ -150,20 +126,6 @@ private class PerceptibleViewStateStoreBox<
init(_ base: Base) {
self.base = base
}

override func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
base.bind(to: keyPath, onChange: onChange)
}

override func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
base.bind(to: keyPath, onChange: onChange)
}
}

@preconcurrency
Expand All @@ -175,55 +137,21 @@ private class PerceptibleViewStateStoreBase<
var viewState: ViewState {
preconditionFailure("Property in abstract base class must be overridden")
}

// swiftlint:disable:next unavailable_function
func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
preconditionFailure("Method in abstract base class must be overridden")
}

// swiftlint:disable:next unavailable_function
func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
preconditionFailure("Method in abstract base class must be overridden")
}
}

// MARK: - Preview

#if DEBUG

@preconcurrency
@MainActor
public final class PerceptiblePreviewStore<ViewState: Equatable>: PerceptibleViewStateStore {

public let viewState: ViewState
public var viewState: ViewState

public init(viewState: ViewState) {
self.viewState = viewState
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
.constant(viewState[keyPath: keyPath])
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
.constant(viewState[keyPath: keyPath])
}
}

#endif

// MARK: - Scope

@Perceptible
Expand All @@ -248,20 +176,6 @@ private final class PerceptibleScope<
self.store = store
self.keyPath = keyPath
}

func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
store.bind(to: self.keyPath.appending(path: keyPath), onChange: onChange)
}

func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
store.bind(to: self.keyPath.appending(path: keyPath), onChange: onChange)
}
}

// MARK: - Store
Expand Down Expand Up @@ -313,30 +227,39 @@ open class PerceptibleStore<
self.viewStateSubject = viewStateSubject
self.transform = transform
}
}

// MARK: - Extensions

extension PerceptibleViewStateStore {

public func scope<T: Equatable>(
viewState keyPath: KeyPath<ViewState, T>
) -> AnyPerceptibleViewStateStore<T> {
AnyPerceptibleViewStateStore(PerceptibleScope(store: self, keyPath: keyPath))
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
Binding(get: { [self] in viewState[keyPath: keyPath] }, set: { onChange($0) })
Binding { [self] in
viewState[keyPath: keyPath]
} set: { value in
onChange(value)
}
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
Binding(get: { [self] in viewState[keyPath: keyPath] }, set: { onChange?($0) })
}
}

// MARK: - Extensions

extension PerceptibleViewStateStore {

public func scope<T: Equatable>(
viewState keyPath: KeyPath<ViewState, T>
) -> AnyPerceptibleViewStateStore<T> {
AnyPerceptibleViewStateStore(PerceptibleScope(store: self, keyPath: keyPath))
guard let onChange: (@MainActor (T) -> Void)
else {
assertionFailure("The `onChange` closure should not be nil")
return bind(to: keyPath) { _ in }
}
return bind(to: keyPath) { onChange($0) }
}
}
```
131 changes: 23 additions & 108 deletions Sources/Nodes/StateManagement/ObservableStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,6 @@ public protocol ObservableViewStateStore<ViewState>: AnyObject, ObservableObject
associatedtype ViewState: Equatable

var viewState: ViewState { get }

func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T>

func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T>
}

// MARK: - State Store
Expand Down Expand Up @@ -120,20 +110,6 @@ public final class AnyObservableViewStateStore<
self?.objectWillChange.send()
}
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
box.bind(to: keyPath, onChange: onChange)
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
box.bind(to: keyPath, onChange: onChange)
}
}

@preconcurrency
Expand All @@ -151,20 +127,6 @@ private class ObservableViewStateStoreBox<
init(_ base: Base) {
self.base = base
}

override func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
base.bind(to: keyPath, onChange: onChange)
}

override func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
base.bind(to: keyPath, onChange: onChange)
}
}

@preconcurrency
Expand All @@ -176,63 +138,21 @@ private class ObservableViewStateStoreBase<
var viewState: ViewState {
preconditionFailure("Property in abstract base class must be overridden")
}

// swiftlint:disable unused_parameter

// swiftlint:disable:next unavailable_function
func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
preconditionFailure("Method in abstract base class must be overridden")
}

// swiftlint:disable:next unavailable_function
func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
preconditionFailure("Method in abstract base class must be overridden")
}

// swiftlint:enable unused_parameter
}

// MARK: - Preview

#if DEBUG

@preconcurrency
@MainActor
public final class ObservablePreviewStore<ViewState: Equatable>: ObservableViewStateStore {

public let viewState: ViewState
public var viewState: ViewState

public init(viewState: ViewState) {
self.viewState = viewState
}

// swiftlint:disable unused_parameter

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
.constant(viewState[keyPath: keyPath])
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
.constant(viewState[keyPath: keyPath])
}

// swiftlint:enable unused_parameter
}

#endif

// MARK: - Scope

@preconcurrency
Expand Down Expand Up @@ -263,20 +183,6 @@ private final class ObservableScope<
self?.objectWillChange.send()
}
}

func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
store.bind(to: self.keyPath.appending(path: keyPath), onChange: onChange)
}

func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
store.bind(to: self.keyPath.appending(path: keyPath), onChange: onChange)
}
}

// MARK: - Store
Expand Down Expand Up @@ -327,30 +233,39 @@ open class ObservableStore<
self.viewStateSubject = viewStateSubject
self.transform = transform
}
}

// MARK: - Extensions

extension ObservableViewStateStore {

public func scope<T: Equatable>(
viewState keyPath: KeyPath<ViewState, T>
) -> AnyObservableViewStateStore<T> {
AnyObservableViewStateStore(ObservableScope(store: self, keyPath: keyPath))
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: @escaping @MainActor (T) -> Void
) -> Binding<T> {
Binding(get: { [self] in viewState[keyPath: keyPath] }, set: { onChange($0) })
Binding { [self] in
viewState[keyPath: keyPath]
} set: { value in
onChange(value)
}
}

public func bind<T>(
to keyPath: KeyPath<ViewState, T>,
onChange: (@MainActor (T) -> Void)?
) -> Binding<T> {
Binding(get: { [self] in viewState[keyPath: keyPath] }, set: { onChange?($0) })
}
}

// MARK: - Extensions

extension ObservableViewStateStore {

public func scope<T: Equatable>(
viewState keyPath: KeyPath<ViewState, T>
) -> AnyObservableViewStateStore<T> {
AnyObservableViewStateStore(ObservableScope(store: self, keyPath: keyPath))
guard let onChange: (@MainActor (T) -> Void)
else {
assertionFailure("The `onChange` closure should not be nil")
return bind(to: keyPath) { _ in }
}
return bind(to: keyPath) { onChange($0) }
}
}

Expand Down
Loading

0 comments on commit 4f774ac

Please sign in to comment.