Last active
August 2, 2023 04:46
-
-
Save BrunoCerberus/6aaf190041434bf258bc76fce0105cf9 to your computer and use it in GitHub Desktop.
Unidirectional Data Flow Architecture
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
import Combine | |
import Foundation | |
/* | |
+----------------+ +------------------------+ +-------------------+ | |
| View | ----> | ViewModel | ----> | Interactor | | |
| | | | | | | |
| ViewEvents | <---- | ViewState DomainMap | <---- | DomainState | | |
+----------------+ +------------------------+ +-------------------+ | |
^ | | |
| v | |
| +---------------------------+ | |
+----| ProductsViewStateReducing | | |
+---------------------------+ | |
*/ | |
// We should only be relying on one published property per domain stack - | |
// adding @Published and @ObservableObject to things like view states or domain states | |
// breaks the unidirectional data flow architecture and results in multiple sources of truth for your view. | |
// ViewModel class for managing the state and interaction of the Products view. | |
final public class ProductsViewModel: CombineViewModel { | |
// The view's current state, which is published to update the UI. | |
@Published public var viewState: ProductsViewState | |
// Interactor to handle domain actions and states. | |
private var interactor: AnyCombineInteractorNoError<ProductsDomainAction, ProductsDomainState> | |
// Reducer to transform domain states into view states. | |
private let reducer: ProductsViewStateReducing | |
// Event-action mapping to convert view events into domain actions. | |
private let domainMap: ProductsEventActionMap | |
// Subject to capture view events from the UI. | |
private let viewEvents = PassthroughSubject<ProductsViewEvent, Never>() | |
// A publisher that maps view events to domain actions and shares the resulting domain state. | |
private lazy var domainState: AnyPublisher<ProductsDomainState, Never> = | |
viewEvents.eraseToAnyPublisher() | |
.map(domainMap.action(from:)) | |
.interact(with: interactor) | |
.share() | |
.eraseToAnyPublisher() | |
// Initializes the ViewModel with initial state, interactor, reducer, and event-action mapping. | |
public init(viewState: ProductsViewState = .loading, | |
interactor: AnyCombineInteractorNoError<ProductsDomainAction, ProductsDomainState>, | |
reducer: ProductsViewStateReducing, | |
domainMap: ProductsEventActionMap) { | |
self.viewState = viewState | |
self.interactor = interactor | |
self.reducer = reducer | |
self.domainMap = domainMap | |
startViewState() // Initiates the view state flow. | |
} | |
// Sets up the view state flow by subscribing to domainState and updating viewState accordingly. | |
private func startViewState() { | |
domainState | |
.map(reducer.reduce(domainState:)) // Transforms domain state to view state. | |
.compactMap { $0 } // Removes any nil view states. | |
.receive(on: DispatchQueue.main) // Ensures UI updates are on the main thread. | |
.assign(to: &$viewState) // Assigns the latest view state to $viewState. | |
} | |
// Function to send view events to the ViewModel. | |
public func sendViewEvent(_ event: ProductsViewEvent) { | |
viewEvents.send(event) // Sends the view event to the viewEvents subject. | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment