• This repository has been archived on 12/Dec/2021
  • Stars
    star
    385
  • Rank 111,464 (Top 3 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 5 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐ŸŒพ Harvest: Apple's Combine.framework + State Machine, inspired by Elm.

NOTE: This repository has been discontinued in favor of Actomaton.

๐ŸŒพ Harvest

Swift 5.1 Build Status

Apple's Combine.framework (from iOS 13) + State Machine, inspired by Elm.

This is a sister library of the following projects:

Requirement

Xcode 11 (Swift 5.1 / macOS 10.15, iOS 13, ...)

Example

To make a state transition diagram like above with additional effects, follow these steps:

1. Define States and Inputs

// 1. Define `State`s and `Input`s.
enum State {
    case loggedOut, loggingIn, loggedIn, loggingOut
}

enum Input {
    case login, loginOK, logout, logoutOK
    case forceLogout
}

2. Define EffectQueue

enum EffectQueue: EffectQueueProtocol {
    case `default`
    case request

    var flattenStrategy: FlattenStrategy {
        switch self {
        case .default: return .merge
        case .request: return .latest
        }
    }

    static var defaultEffectQueue: EffectQueue {
        .default
    }
}

EffectQueue allows additional side-effects (Effect, a wrapper of Publisher) to be scheduled with a specific FlattenStrategy, such as flatMap (.merge), flatMapLatest (.latest), etc. In above case, we want to automatically cancel previous network requests if occurred multiple times, so we also prepare case request queue with .latest strategy.

3. Create EffectMapping (Effect-wise reducer)

// NOTE: `EffectID` is useful for manual effect cancellation, but not used in this example.
typealias EffectID = Never

typealias Harvester = Harvest.Harvester<Input, State>
typealias EffectMapping = Harvester.EffectMapping<EffectQueue, EffectID>
typealias Effect = Harvester.Effect<Input, EffectQueue, EffectID>

// Additional effects while state-transitioning.
let loginOKPublisher = /* show UI, setup DB, request APIs, ..., and send `Input.loginOK` */
let logoutOKPublisher = /* show UI, clear cache, cancel APIs, ..., and send `Input.logoutOK` */
let forceLogoutOKPublisher = /* do something more special, ..., and send `Input.logoutOK` */

let canForceLogout: (State) -> Bool = [.loggingIn, .loggedIn].contains

let mappings: [EffectMapping] = [

  /*  Input   |   fromState => toState     |      Effect       */
  /* ----------------------------------------------------------*/
    .login    | .loggedOut  => .loggingIn  | Effect(loginOKPublisher, queue: .request),
    .loginOK  | .loggingIn  => .loggedIn   | .empty,
    .logout   | .loggedIn   => .loggingOut | Effect(logoutOKPublisher, queue: .request),
    .logoutOK | .loggingOut => .loggedOut  | .empty,

    .forceLogout | canForceLogout => .loggingOut | Effect(forceLogoutOKPublisher, queue: .request)
]

EffectMapping is Redux's Reducer or Elm's Update pure function that also returns Effect during the state-transition. Note that queue: .request is specified so that those effects will be handled in the same queue with .latest strategy. Instead of writing it as a plain function with pattern-matching, you can also write in a fancy markdown-table-like syntax as shown above.

4. Setup Harvester (state machine)

// Prepare input pipe for sending `Input` to `Harvester`.
let inputs = PassthroughSubject<Input, Never>()

var cancellables: [AnyCancellable] = []

// Setup state machine.
let harvester = Harvester(
    state: .loggedOut,
    input: inputs,
    mapping: .reduce(.first, mappings),  // combine mappings using `reduce` helper
    scheduler: DispatchQueue.main
)

// Observe state-transition replies (`.success` or `.failure`).
harvester.replies.sink { reply in
    print("received reply = \(reply)")
}.store(in: &cancellables)

// Observe current state changes.
harvester.state.sink { state in
    print("current state = \(state)")
}.store(in: &cancellables)

NOTE: func reduce is declared to combine multiple mappings into one.

5. And let's test!

let send = inputs.send

expect(harvester.state) == .loggedIn    // already logged in
send(Input.logout)
expect(harvester.state) == .loggingOut  // logging out...
// `logoutOKPublisher` will automatically send `Input.logoutOK` later
// and transit to `State.loggedOut`.

expect(harvester.state) == .loggedOut   // already logged out
send(Input.login)
expect(harvester.state) == .loggingIn   // logging in...
// `loginOKPublisher` will automatically send `Input.loginOK` later
// and transit to `State.loggedIn`.

// ๐Ÿ‘จ๐Ÿฝ < But wait, there's more!
// Let's send `Input.forceLogout` immediately after `State.loggingIn`.

send(Input.forceLogout)                       // ๐Ÿ’ฅ๐Ÿ’ฃ๐Ÿ’ฅ
expect(harvester.state) == .loggingOut  // logging out...
// `forceLogoutOKublisher` will automatically send `Input.logoutOK` later
// and transit to `State.loggedOut`.

Please notice how state-transitions, effect calls and cancellation are nicely performed. If your cancellation strategy is more complex than just using FlattenStrategy.latest, you can also use Effect.cancel to manually stop specific EffectID.

Note that any sizes of State and Input will work using Harvester, from single state (like above example) to covering whole app's states (like React.js + Redux architecture).

Using Feedback effect model as alternative

Instead of using EffectMapping with fine-grained EffectQueue model, Harvest also supports Feedback system as described in the following libraries:

See inamiy/ReactiveAutomaton#12 for more discussion.

Composable Architecture with SwiftUI

Pull Request #8 introduced HarvestStore and HarvestOptics frameworks for Composable Architecture, especially focused on SwiftUI.

  • HarvestStore: 2-way bindable Store optimized for SwiftUI
  • HarvestOptics: Input & state lifting helpers using FunOptics

See Harvest-SwiftUI-Gallery for the examples.

See also Babylonpartners/ios-playbook#171 for further related discussion.

  • TODO: Write documentation

References

  1. ReactiveAutomaton (using ReactiveSwift)
  2. RxAutomaton (using RxSwift)
  3. iOSDC 2016 (Tokyo, in Japanese) (2016/08/20)
  4. iOSConf SG (Singapore, in English) (2016/10/20-21)

License

MIT

More Repositories

1

SwiftRewriter

๐Ÿ“ Swift code formatter using SwiftSyntax.
Swift
825
star
2

RxAutomaton

๐Ÿค– RxSwift + State Machine, inspired by Redux and Elm.
Swift
715
star
3

Cassowary

An incremental linear constraint-solving algorithm (Auto Layout) in Swift.
Swift
501
star
4

YIPopupTextView

facebook's post-like input text view for iOS (Beerware license)
Objective-C
245
star
5

YIFullScreenScroll

Pinterest-like scroll-to-fullscreen UI for iOS5+.
Objective-C
214
star
6

ReactiveAutomaton

๐Ÿค– ReactiveCocoa + State Machine, inspired by Redux and Elm.
Swift
207
star
7

Harvest-SwiftUI-Gallery

๐Ÿ–ผ Gallery App for Harvest (Elm Architecture + Optics) + SwiftUI + Combine.
Swift
161
star
8

YIInnerShadowView

Inner-shadow UIView/CALayer for iOS.
Objective-C
155
star
9

YISplashScreen

Easy splash screen + animation maker for iOS5+.
Objective-C
141
star
10

Actomaton

๐ŸŽญ Swift async/await & Actor-powered effectful state-management framework.
Swift
139
star
11

SherlockForms

๐Ÿ•ต๏ธโ€โ™‚๏ธ An elegant SwiftUI Form builder to create a searchable Settings and DebugMenu screens for iOS.
Swift
124
star
12

SwiftElm

Reactive + Automaton + VTree in Swift, inspired by Elm.
Swift
103
star
13

VTree

VirtualDOM for Swift (iOS, macOS)
Swift
91
star
14

RxProperty

A get-only `BehaviorRelay ` that is (almost) equivalent to ReactiveSwift's `Property`
Swift
85
star
15

FunRouter

Functional & type-safe URL routing example for http://2016.funswiftconf.com
Swift
82
star
16

Swizzle

Method-Swizzling for Swift.
Swift
81
star
17

Flexbox

Swift wrapper of facebook/yoga (CSS Flexbox layout engine).
Swift
76
star
18

Zelkova

Elm/React.js-like architecture in Swift, powered by ReactiveSwift and LayoutKit.
Swift
69
star
19

ReactiveCocoaCatalog

UI Catalog for ReactiveCocoa.
Swift
60
star
20

YISwipeShiftCaret

Swipe-to-shift text input caret for iOS (no private APIs)
Objective-C
47
star
21

DebugLog

DebugLog macro alternative for Swift.
Swift
44
star
22

HigherKindSwift

An experimental Higher Kinded Types in Swift.
Swift
44
star
23

Await

Swift port of C# Await using Cocoa's Run Loop mechanism.
Swift
43
star
24

MultibyteDescription

A better way to NSLog multibyte string for OSX/iOS. (see also: http://qiita.com/items/85437eba2623f6ffbdbd)
Objective-C
41
star
25

YIDragScrollBar

Attaches draggable scroll bar on top of original UIScrollView for iOS5+, works like a drug.
Objective-C
37
star
26

Actomaton-Gallery

๐Ÿ–ผ Gallery App for Actomaton (async/await + Elm Architecture) + SwiftUI.
Swift
30
star
27

YIDetectWindow

A subclass of UIWindow for detecting shake, status-bar-tap, long-press, touchBegan/Moved/Ended/Cancelled, via NSNotification.
Objective-C
29
star
28

FunAsync

โณ Collection of Swift 5.5 async/await utility functions.
Swift
25
star
29

Harvest-SwiftUI-GameOfLife

๐Ÿงฌ Conway's Game of Life written in SwiftUI + Harvest
Swift
24
star
30

YIEmoji

NSString addition for iOS Emoji.
Objective-C
23
star
31

Swift-Intersection

Extensible records / intersection type in Swift.
Swift
21
star
32

SwiftUI-PhotoPicker

iOS 14 PHPickerViewController wrapper for SwiftUI with data loader support.
Swift
20
star
33

YIStrictEdgePanGesture

Never get angry with UINavigationController's interactivePopGestureRecognizer.
Objective-C
18
star
34

ImagePlaceholder

Yet another UIImage / NSImage placeholder written in Swift.
Swift
18
star
35

SwiftAndLogic

Sample code for iOSDC Japan 2019 and NSSpain 2019
Swift
16
star
36

OrientationKit

iOS device/interface/image/video orientation translation & detection using CoreMotion + SwiftUI + Combine.
Swift
14
star
37

ShapeLayerView

CAShapeLayer-backed UIView subclass that synchronizes with UIKit-internal animations, e.g. orientation change.
Swift
12
star
38

AsyncHotStream

โ™จ๏ธ A missing hot stream in Swift Concurrency.
Swift
12
star
39

YIEdgePanGestureRecognizer

A subclass of UIPanGestureRecognizer which only activates at the edge of the view.
Objective-C
12
star
40

Swift-Union

Poor man's untagged union type in Swift.
Swift
11
star
41

YIHideableTabBar

UITabBarController category to show/hide UITabBar for iOS.
Objective-C
11
star
42

Harvest-SwiftUI-VideoDetector

๐Ÿ“น Video image/text recognizers written in SwiftUI + Harvest + iOS Vision + SwiftyTesseract
Swift
11
star
43

YIVariableViewSize

Layout subviews first, then its container. Not using AutoLayout, works on iOS5+.
Objective-C
10
star
44

Swift-Lens-Example

Swift Lens example
Swift
9
star
45

FunOptics

๐Ÿ”Simple functional Optics in Swift
Swift
9
star
46

AVFoundation-Combine

AVFoundation + Combine extensions
Swift
8
star
47

DDFileReader

Swift port of DDFileReader by Dave DeLong (http://stackoverflow.com/a/3711079/666371)
Swift
7
star
48

YILogHook

NSLog-Hook using _NSSetLogCStringFunction (private API)
Objective-C
7
star
49

AnyScheduler

iOS 13 Combine's type-erased AnyScheduler.
Swift
6
star
50

YITimeTracker

A simple time-tracking tool which can easily integrate with other libraries e.g. SVProgressHUD, MTStatusBarOverlay.
Objective-C
5
star
51

iOS6-ForwardAutorotate

UIKit-additions to forward iOS6 rotation methods.
Objective-C
5
star
52

MNIST-iOS-Demo

MNIST-iOS demo with PyTorch -> ONNX -> CoreML conversion
Swift
3
star
53

appstore-node-coffee

AppStore review scraper using node+CoffeeScirpt
CoffeeScript
3
star
54

YIRightTouchableToolbar

Bug fix for right UIBarButtonItem not responding at bottom toolbar in iOS 7.0.3.
Objective-C
3
star
55

iOS15-SwiftUI-Navigation-Bug

Demonstrates SwiftUI Navigation behavior change from iOS 14 to iOS 15 which disallows single-source-of-truth state management.
3
star
56

YICustomModal

Custom modal, mainly for iOS5 youtube-fullscreen-dismiss bug (see also: https://github.com/inamiy/ModalYoutubeIOS5Bug)
Objective-C
2
star
57

YIHorizontalTableView

Transformed-UITableView to achieve horizontal scrolling for iOS.
Objective-C
1
star
58

inamiy

Welcome to a special repository!
1
star
59

YIPickerActionSheet

UIActionSheet+UIPickerView for iOS
Objective-C
1
star
60

inamiy.github.com

1
star
61

ToAnyObject

Cocoa-friendly AnyObject (and JSON) minimal encoder using Mirror API.
Swift
1
star
62

YINilHandling

NSArray/NSDictionary categories to nullify/ignore nil value for iOS.
Objective-C
1
star
63

Log-YIHelper

log macros for iOS
C++
1
star
64

ModalYoutubeIOS5Bug

Modal+WebView+Youtube Bug, found in iOS5.1 (It's now OK in iOS6 beta 2)
Objective-C
1
star
65

iOS7-ToolbarTouchBug

Demo for right UIBarButtonItem not responding at bottom toolbar in iOS 7.0.3.
Objective-C
1
star
66

Test

Test
1
star
67

github-experiment

Shell
1
star