Skip to content
This repository has been archived by the owner on May 12, 2020. It is now read-only.

Commit

Permalink
Introduce StatePublished.
Browse files Browse the repository at this point in the history
  • Loading branch information
andersio committed Jun 10, 2019
1 parent 6748757 commit 8c8db31
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 30 deletions.
Expand Up @@ -30,58 +30,106 @@
filePath = "Example/CounterViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "25"
endingLineNumber = "25"
startingLineNumber = "27"
endingLineNumber = "27"
landmarkName = "init()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "2AED3115-F316-4B1F-BF45-71171CE0AE5B"
uuid = "6D343CAF-5DAF-40FB-B240-DE2A49E1A0A4"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "ViewModel/FeedbackLoop.swift"
filePath = "Example/CounterViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "46"
endingLineNumber = "46"
landmarkName = "init(initial:reduce:feedbacks:)"
startingLineNumber = "31"
endingLineNumber = "31"
landmarkName = "init()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "6D343CAF-5DAF-40FB-B240-DE2A49E1A0A4"
uuid = "8EB36DA4-54EA-4FD2-BE06-610C0F9482F2"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Example/CounterViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "29"
endingLineNumber = "29"
startingLineNumber = "41"
endingLineNumber = "41"
landmarkName = "init()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "8EB36DA4-54EA-4FD2-BE06-610C0F9482F2"
uuid = "5EB2CB92-F28A-427D-BEBE-389371BF0F9F"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Example/CounterViewModel.swift"
filePath = "ViewModel/FeedbackLoop.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "38"
endingLineNumber = "38"
landmarkName = "init()"
startingLineNumber = "77"
endingLineNumber = "77"
landmarkName = "process(_:willReduce:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "C7FEBCFC-9DE7-42C9-A5F1-B487EC6906F5"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "ViewModel/FeedbackLoop.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "52"
endingLineNumber = "52"
landmarkName = "init(initial:reduce:feedbacks:)"
landmarkType = "7">
<Locations>
<Location
uuid = "C7FEBCFC-9DE7-42C9-A5F1-B487EC6906F5 - 9992d3f5c7e52f44"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #2 (B) -&gt; () in Cycler.FeedbackLoop.init(initial: A, reduce: (inout A, Cycler.FeedbackLoop&lt;A, B, C&gt;.Input) -&gt; (), feedbacks: Swift.Array&lt;Cycler.FeedbackLoop&lt;A, B, C&gt;.Feedback&gt;) -&gt; Cycler.FeedbackLoop&lt;A, B, C&gt;"
moduleName = "Cycler"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/anders.ha/repos/Cycler/ViewModel/FeedbackLoop.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "52"
endingLineNumber = "52"
offsetFromSymbolStart = "340">
</Location>
<Location
uuid = "C7FEBCFC-9DE7-42C9-A5F1-B487EC6906F5 - cf9b9b509e13d5d0"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #1 (inout A) -&gt; () in closure #2 (B) -&gt; () in Cycler.FeedbackLoop.init(initial: A, reduce: (inout A, Cycler.FeedbackLoop&lt;A, B, C&gt;.Input) -&gt; (), feedbacks: Swift.Array&lt;Cycler.FeedbackLoop&lt;A, B, C&gt;.Feedback&gt;) -&gt; Cycler.FeedbackLoop&lt;A, B, C&gt;"
moduleName = "Cycler"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/anders.ha/repos/Cycler/ViewModel/FeedbackLoop.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "52"
endingLineNumber = "52"
offsetFromSymbolStart = "16">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
Expand Down
48 changes: 33 additions & 15 deletions ViewModel/FeedbackLoop.swift
Expand Up @@ -28,31 +28,29 @@ open class FeedbackLoop<State, Event, Action>: ViewModel {
}
}

public private(set) var state: State
@StatePublished public var state: State

public let didChange: PassthroughSubject<Void, Never>
private let outputSubject: PassthroughSubject<Output, Never>
private let outputSubject: CurrentValueSubject<Output, Never>
private let reduce: (inout State, Input) -> Void

public init(
initial: State,
reduce: @escaping (inout State, Input) -> Void,
feedbacks: [Feedback] = []
) {
self.state = initial
self.reduce = reduce
self.didChange = PassthroughSubject()
self.outputSubject = PassthroughSubject()
self.outputSubject = CurrentValueSubject(Output(state: initial, input: nil))
self.$state = .init(subject: outputSubject)

_ = Publishers.MergeMany(
feedbacks.map { $0.effects(AnyPublisher(outputSubject)) }
)
.sink { [weak self] event in
guard let self = self else { return }
self.process(.event(event))
self.process(.event(event)) { _ in }
}

outputSubject.send(Output(state: initial, input: nil))
}

deinit {
Expand All @@ -61,21 +59,41 @@ open class FeedbackLoop<State, Event, Action>: ViewModel {
}

public func perform(_ action: Action) {
mainThreadAssertion()
process(.action(action))
process(.action(action)) { _ in }
}

public func update<U>(_ value: U, for keyPath: WritableKeyPath<State, U>) {
mainThreadAssertion()
self.state[keyPath: keyPath] = value
process(.updated(keyPath))
process(.updated(keyPath)) {
$0[keyPath: keyPath] = value
}
}

private func process(_ input: Input) {
reduce(&self.state, input)
outputSubject.send(Output(state: state, input: input))
private func process(_ input: Input, willReduce: (inout State) -> Void) {
mainThreadAssertion()

var state = outputSubject.value.state
willReduce(&state)
reduce(&state, input)
outputSubject.value = Output(state: state, input: input)
didChange.send(())
}

@propertyDelegate
public struct StatePublished {
public var value: State {
_read { yield subject.value.state }
}

public var publisher: AnyPublisher<State, Never> {
return subject.map { $0.state }.eraseToAnyPublisher()
}

private let subject: CurrentValueSubject<Output, Never>

fileprivate init(subject: CurrentValueSubject<Output, Never>) {
self.subject = subject
}
}
}

private func mainThreadAssertion() {
Expand Down

0 comments on commit 8c8db31

Please sign in to comment.