Unidirectional Input / Output framework with Combine. Supports both of SwiftUI and UIKit.

Ricemill represents unidirectional data flow with these components.




The rule of Input is having Subject properties that are defined internal scope.

struct Input: InputType {
    let increment = PassthroughSubject<Void, Never>()
    let isOn = PassthroughSubject<Bool, Never>()

Properties of Input are defined internal scope. But these return SubjectProxy via dynamicMemberLookup if Input is wrapped with InputProxy.

let input: InputProxy<Input>
let increment: SubjectProxy<Void> = input.increment
let isOn: SubjectProxy<Bool> = input.isOn


The rule of Output is having Publisher or @Published properties that are defined internal scope.

class Output: OutputType {
    let count: AnyPublisher<String?, Never>
    @Published var isIncrementEnabled: Bool


The rule of Store is having inner states.

class Store: StoreType {
    @Published var count = 0
    @Published var isIncrementEnabled: Bool = false


The rule of Extra is having other dependencies.


The rule of Resolver is generating Output from Input, Store and Extra. It generates Output to call static func polish(input:store:extra:). static func polish(input:store:extra:) is called once when Machine is initialized.

enum Resolver: ResolverType {
    typealias Input = ViewModel.Input
    typealias Output = ViewModel.Output
    typealias Store = ViewModel.Store
    typealias Extra = ViewModel.Extra

    static func polish(input: Publishing<Input>, store: Store, extra: Extra) -> Polished<Output> {

Here is a exmaple of implementation of static func polish(input:store:extra:).

extension ViewModel.Resolver {

    static func polish(input: Publishing<Input>,
                       store: Store,
                       extra: Extra) -> Polished<Output> {

         var cancellables: [AnyCancellable] = []

         let increment = input.increment
             .flatMap { _ in Just(store.count) }
             .map { $0 + 1 }

         increment.merge(with: decrement)
             .assign(to: \.count, on: store)
             .store(in: &cancellables)

         let count = store.$count

         return Polished(output: Output(count: count),
                         cancellables: cancellables)


Machine represents ViewModels of MVVM (it can also be used as Models). It has input: InputProxy<Input> and output: OutputProxy<Output>. It automatically generates input: InputProxy<Input> and output: OutputProxy<Output> from instances of Input, Store, Extra and Resolver.

SwiftUI Usage

If Input implements BindableInputType, can access value as Binding<Value> from outside.
In addition, if Output equals Store and implements StoredOutputType, can access primitive value and Publisher from outside.
Sample implementaion is here.

extension ViewModel {
    typealias Output = Store

    final class Input: BindableInputType {
        let increment = PassthroughSubject<Void, Never>()
        @Published var isOn = false

    final class Store: StoredOutputType {
        @Published var count: Int = 0

let viewModel: ViewModel = ...
viewModel.input.isOn    // This is `Binding<Bool>` instance.
viewModel.output.count  // This is `Int` instance.
viewModel.output.$count // This is `Published<Int>.Publisher` instance.


  • Xcode 11 Beta 5
  • macOS 10.15
  • iOS 13.0
  • tvOS 13.0
  • watchOS 6.0

Other links