diff --git a/Cycler.xcodeproj/xcuserdata/anders.ha.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Cycler.xcodeproj/xcuserdata/anders.ha.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index c9a28ef..7f66f5b 100644 --- a/Cycler.xcodeproj/xcuserdata/anders.ha.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Cycler.xcodeproj/xcuserdata/anders.ha.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -30,8 +30,8 @@ filePath = "Example/CounterViewModel.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "25" - endingLineNumber = "25" + startingLineNumber = "27" + endingLineNumber = "27" landmarkName = "init()" landmarkType = "7"> @@ -39,31 +39,31 @@ @@ -71,17 +71,65 @@ + + + + + + + + + + diff --git a/ViewModel/FeedbackLoop.swift b/ViewModel/FeedbackLoop.swift index b9aa806..6730881 100644 --- a/ViewModel/FeedbackLoop.swift +++ b/ViewModel/FeedbackLoop.swift @@ -28,10 +28,10 @@ open class FeedbackLoop: ViewModel { } } - public private(set) var state: State + @StatePublished public var state: State public let didChange: PassthroughSubject - private let outputSubject: PassthroughSubject + private let outputSubject: CurrentValueSubject private let reduce: (inout State, Input) -> Void public init( @@ -39,20 +39,18 @@ open class FeedbackLoop: ViewModel { 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 { @@ -61,21 +59,41 @@ open class FeedbackLoop: ViewModel { } public func perform(_ action: Action) { - mainThreadAssertion() - process(.action(action)) + process(.action(action)) { _ in } } public func update(_ value: U, for keyPath: WritableKeyPath) { - 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 { + return subject.map { $0.state }.eraseToAnyPublisher() + } + + private let subject: CurrentValueSubject + + fileprivate init(subject: CurrentValueSubject) { + self.subject = subject + } + } } private func mainThreadAssertion() {