Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/marty-suzuki/ricemill
🌾 ♻️ 🍚 Unidirectional Input / Output framework with Combine. Supports both of SwiftUI and UIKit.
https://github.com/marty-suzuki/ricemill
combine mvvm swift swiftui
Last synced: 2 months ago
JSON representation
🌾 ♻️ 🍚 Unidirectional Input / Output framework with Combine. Supports both of SwiftUI and UIKit.
- Host: GitHub
- URL: https://github.com/marty-suzuki/ricemill
- Owner: marty-suzuki
- License: mit
- Created: 2019-08-15T04:00:39.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2021-03-31T14:55:53.000Z (almost 4 years ago)
- Last Synced: 2024-10-15T12:22:55.044Z (3 months ago)
- Topics: combine, mvvm, swift, swiftui
- Language: Swift
- Homepage:
- Size: 22.5 KB
- Stars: 63
- Watchers: 4
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Ricemill
🌾 ♻️ 🍚 Unidirectional Input / Output framework with Combine.
| [SwiftUI Playground](https://github.com/marty-suzuki/Ricemill/blob/master/Ricemill.playground/Pages/SwiftUI.xcplaygroundpage/Contents.swift) | [UIKit Playground](https://github.com/marty-suzuki/Ricemill/blob/master/Ricemill.playground/Pages/UIKit.xcplaygroundpage/Contents.swift) |
| :-: | :-: |
| ![SwiftUI](https://user-images.githubusercontent.com/2082134/63072558-68a5b780-bf5f-11e9-81e8-d25798ec29da.gif) | ![UIKit](https://user-images.githubusercontent.com/2082134/63072557-67748a80-bf5f-11e9-9f9f-fe6510421340.gif) |# About Ricemill
Ricemill represents unidirectional data flow with these components.
- [Input](#input)
- [Output](#output)
- [Store](#store)
- [Extra](#extra)
- [Resolver](#resolver)
- [Machine](#machine)### Input
The rule of Input is having Subject properties that are defined internal scope.
```swift
struct Input: InputType {
let increment = PassthroughSubject()
let isOn = PassthroughSubject()
}
```Properties of Input are defined internal scope. But these return `SubjectProxy` via dynamicMemberLookup if Input is wrapped with InputProxy.
```swift
let input: InputProxy
let increment: SubjectProxy = input.increment
increment.send()
let isOn: SubjectProxy = input.isOn
isOn.send(true)
```### Output
The rule of Output is having Publisher or `@Published` properties that are defined internal scope.
```swift
class Output: OutputType {
let count: AnyPublisher
@Published var isIncrementEnabled: Bool
}
```### Store
The rule of Store is having inner states.
```swift
class Store: StoreType {
@Published var count = 0
@Published var isIncrementEnabled: Bool = false
}
```### Extra
The rule of Extra is having other dependencies.
### Resolver
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.
```swift
enum Resolver: ResolverType {
typealias Input = ViewModel.Input
typealias Output = ViewModel.Output
typealias Store = ViewModel.Store
typealias Extra = ViewModel.Extrastatic func polish(input: Publishing, store: Store, extra: Extra) -> Polished {
...
}
}
```Here is a exmaple of implementation of `static func polish(input:store:extra:)`.
```swift
extension Resolver {static func polish(input: Publishing,
store: Store,
extra: Extra) -> Polished {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
.map(String.init)
.map(Optional.some)
.eraseToAnyPublisher()return Polished(output: Output(count: count),
cancellables: cancellables)
}
}
```### Machine
Machine represents ViewModels of MVVM (it can also be used as Models). It has `input: InputProxy` and `output: OutputProxy`. It automatically generates `input: InputProxy` and `output: OutputProxy` from instances of [Input](#input), [Store](#store), [Extra](#extra) and [Resolver](#resolver).
```swift
final class ViewModel: Machine {final class Input: InputType {
let increment = PassthroughSubject()
let decrement = PassthroughSubject()
}final class Store: StoredOutputType {
@Published var count: Int = 0
}final class Output: OutputType {
let count: AnyPublisher
}struct Extra: ExtraType {}
static func polish(
input: Publishing,
store: Store,
extra: Extra
) -> Polished {
var cancellables: [AnyCancellable] = []let increment = input.increment
.flatMap { _ in Just(store.count) }
.map { $0 + 1 }let decrement = input.decrement
.flatMap { _ in Just(store.count) }
.map { $0 - 1 }increment.merge(with: decrement)
.assign(to: \.count, on: store)
.store(in: &cancellables)let count = store.$count
.map(String.init)
.map(Optional.some)
.eraseToAnyPublisher()return Polished(output: Output(count: count),
cancellables: cancellables)
}
}
```#### SwiftUI Usage
If Input implements `BindableInputType`, can access value as `Binding` from outside.
In addition, if Output equals Store and implements `StoredOutputType`, can access primitive value and Publisher from outside.
Sample implementaion is here.```swift
final class ViewModel: Machine {
typealias Output = Storefinal class Input: BindableInputType {
let increment = PassthroughSubject()
let decrement = PassthroughSubject()
}final class Store: StoredOutputType {
@Published var count: Int = 0
}struct Extra: ExtraType {}
static func polish(
input: Publishing,
store: Store,
extra: Extra
) -> Polished {
var cancellables: [AnyCancellable] = []let increment = input.increment
.flatMap { _ in Just(store.count) }
.map { $0 + 1 }let decrement = input.decrement
.flatMap { _ in Just(store.count) }
.map { $0 - 1 }increment.merge(with: decrement)
.assign(to: \.count, on: store)
.store(in: &cancellables)return Polished(cancellables: cancellables)
}
}let viewModel: ViewModel = ...
viewModel.input.isOn // This is `Binding` instance.
viewModel.output.count // This is `Int` instance.
viewModel.output.$count // This is `Published.Publisher` instance.
```# Requirement
- Xcode 12
- macOS 10.15
- iOS 13.0
- tvOS 13.0
- watchOS 6.0# Other links
- [cats-oss/Unio](https://github.com/cats-oss/Unio)
- A sister library of Ricemill that runs on RxSwift
- [GitHubSearchWithSwiftUI](https://github.com/marty-suzuki/GitHubSearchWithSwiftUI/blob/ricemill-sample/GitHubSearchWithSwiftUI/View/RepositoryListViewModel.swift)
- An example of GitHub Repository Search App with Ricemill![screenshot](https://user-images.githubusercontent.com/2082134/63103899-ef3ab300-bfb8-11e9-89d4-2c7f5f1a73da.png)
# License
Ricemill is released under the MIT License.