Skip to content

Commit

Permalink
project: remove scheduling from reducers
Browse files Browse the repository at this point in the history
  • Loading branch information
twittemb committed Aug 8, 2020
1 parent db717d3 commit 00f447c
Show file tree
Hide file tree
Showing 83 changed files with 1,241 additions and 2,386 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
**version 0.18.0**:

* breaking: Executers are no longer associated to a Reducer but to the whole Spin instead (and can still be overriden in each Feedback)

**version 0.17.0**:

* introduce Gear: a mediator pattern between Spins that allows them to communicate together
Expand Down
2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "ReactiveX/RxSwift" ~> 5.1.1
github "ReactiveCocoa/ReactiveSwift" ~> 6.1
github "ReactiveCocoa/ReactiveSwift" ~> 6.3.0
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "ReactiveCocoa/ReactiveSwift" "6.2.1"
github "ReactiveCocoa/ReactiveSwift" "6.3.0"
github "ReactiveX/RxSwift" "5.1.1"
44 changes: 4 additions & 40 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,58 +1,22 @@
{
"object": {
"pins": [
{
"package": "CwlCatchException",
"repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git",
"state": {
"branch": null,
"revision": "7cd2f8cacc4d22f21bc0b2309c3b18acf7957b66",
"version": "1.2.0"
}
},
{
"package": "CwlPreconditionTesting",
"repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git",
"state": {
"branch": null,
"revision": "c228db5d2ad1b01ebc84435e823e6cca4e3db98b",
"version": "1.2.0"
}
},
{
"package": "Nimble",
"repositoryURL": "https://github.com/Quick/Nimble.git",
"state": {
"branch": null,
"revision": "b02b00b30b6353632aa4a5fb6124f8147f7140c0",
"version": "8.0.5"
}
},
{
"package": "Quick",
"repositoryURL": "https://github.com/Quick/Quick.git",
"state": {
"branch": null,
"revision": "33682c2f6230c60614861dfc61df267e11a1602f",
"version": "2.2.0"
}
},
{
"package": "ReactiveSwift",
"repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift",
"state": {
"branch": null,
"revision": "e27ccdbf4ec36f154b60b91a0d7e0110c4e882cb",
"version": "6.2.1"
"revision": "3f4351d04115fd8797802d9b2d17b812cd761602",
"version": "6.3.0"
}
},
{
"package": "RxSwift",
"repositoryURL": "https://github.com/ReactiveX/RxSwift",
"state": {
"branch": null,
"revision": "c1bd31b397d87a54467af4161dde9d6b27720c19",
"version": "5.1.0"
"revision": "002d325b0bdee94e7882e1114af5ff4fe1e96afa",
"version": "5.1.1"
}
}
]
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ let package = Package(
targets: ["SpinRxSwift"]),
],
dependencies: [
.package(url: "https://github.com/ReactiveCocoa/ReactiveSwift", from: "6.2.1"),
.package(url: "https://github.com/ReactiveX/RxSwift", from: "5.1.0"),
.package(url: "https://github.com/ReactiveCocoa/ReactiveSwift", from: "6.3.0"),
.package(url: "https://github.com/ReactiveX/RxSwift", from: "5.1.1"),
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<img alt="Spin Logo" src="https://raw.githubusercontent.com/Spinners/Spin.Swift/master/Resources/spin-logo.png" height="250"/>
</p>

**With the recent introduction of Combine and SwiftUI, we will face some transition periods in our code base. Our applications will use both Combine and a third-party reactive framework, or both UIKit and SwiftUI, which makes it potentially difficult to guarantee a consistent architecture over time.**
**With the introduction of Combine and SwiftUI, we will face some transition periods in our code base. Our applications will use both Combine and a third-party reactive framework, or both UIKit and SwiftUI, which makes it potentially difficult to guarantee a consistent architecture over time.**

**Spin is a tool to build feedback loops within a Swift based application allowing you to use a unified syntax whatever the underlying reactive programming framework and whatever Apple UI technology you use (RxSwift, ReactiveSwift, Combine and UIKit, AppKit, SwiftUI).**

Expand Down Expand Up @@ -251,19 +251,21 @@ Choose wisely the option that fits your needs. Not cancelling previous operation

Reactive programming is often associated with asynchronous execution. Even though every reactive framework comes with its own GCD abstraction, it is always about stating which scheduler the side effect should be executed on.

Spin provides a way to specify this scheduler for each feedback you add to a loop while still being as declarative as possible:
By default, a Spin will be executed on a background thread created by the framework.

However, Spin provides a way to specify a scheduler for the Spin it-self and for each feedback you add to it:

```swift
Spinner
.initialState(Levels(left: 10, right: 20))
.initialState(Levels(left: 10, right: 20), executeOn: MainScheduler.instance)
.feedback(Feedback(effect: leftEffect, on: SerialDispatchQueueScheduler(qos: .userInitiated)))
.feedback(Feedback(effect: rightEffect, on: SerialDispatchQueueScheduler(qos: .userInitiated)))
.reducer(Reducer(levelsReducer))
```
or

```swift
Spin(initialState: Levels(left: 10, right: 20)) {
Spin(initialState: Levels(left: 10, right: 20), executeOn: MainScheduler.instance) {
Feedback(effect: leftEffect)
.execute(on: SerialDispatchQueueScheduler(qos: .userInitiated))
Feedback(effect: rightEffect)
Expand Down Expand Up @@ -486,7 +488,7 @@ https://github.com/Spinners/Spin.Swift.git
Add the following entry to your Cartfile:

```
github "Spinners/Spin.Swift" ~> 0.17.0
github "Spinners/Spin.Swift" ~> 0.18.0
```

and then:
Expand All @@ -500,9 +502,9 @@ carthage update Spin.Swift
Add the following dependencies to your Podfile:

```
pod 'SpinReactiveSwift', '~> 0.17.0'
pod 'SpinCombine', '~> 0.17.0'
pod 'SpinRxSwift', '~> 0.17.0'
pod 'SpinReactiveSwift', '~> 0.18.0'
pod 'SpinCombine', '~> 0.18.0'
pod 'SpinRxSwift', '~> 0.18.0'
```

You should then be able to import SpinCommon (base implementation), SpinRxSwift, SpinReactiveSwift or SpinCombine
Expand Down
15 changes: 0 additions & 15 deletions Sources/Combine/AnyCancellable+DisposeBag.swift

This file was deleted.

35 changes: 21 additions & 14 deletions Sources/Combine/AnyPublisher+streamFromSpin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,36 @@

import Combine
import SpinCommon
import Dispatch

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public extension AnyPublisher where Failure == Never {
static func stream<Event>(from spin: Spin<Value, Event>) -> AnyPublisher<Value, Never> {
return Deferred<AnyPublisher<Value, Never>> { [weak spin] in
static func stream<Event, Executer>(from spin: ScheduledSpin<Value, Event, Executer>) -> AnyPublisher<Value, Never>
where
Executer: ExecuterDefinition,
Executer.Executer: Scheduler {
return Deferred<AnyPublisher<Value, Never>> { [weak spin] in

guard let spin = spin else { return Empty().eraseToAnyPublisher() }
guard let spin = spin else { return Empty().eraseToAnyPublisher() }

let currentState = CurrentValueSubject<Value, Never>(spin.initialState)
let currentState = CurrentValueSubject<Value, Never>(spin.initialState)

// merging all the effects into one event stream
let eventStreams = spin.effects.map { $0(currentState.eraseToAnyPublisher()) }
let eventStream = Publishers.MergeMany(eventStreams).eraseToAnyPublisher()
// merging all the effects into one event stream
let stateInputStream = currentState.eraseToAnyPublisher()
let eventStreams = spin.effects.map { $0(stateInputStream) }
let eventStream = Publishers.MergeMany(eventStreams).eraseToAnyPublisher()

return spin
.scheduledReducer(eventStream)
.prepend(spin.initialState)
.handleEvents(receiveOutput: currentState.send)
.eraseToAnyPublisher()
}.eraseToAnyPublisher()
return eventStream
.subscribe(on: spin.executer)
.receive(on: spin.executer)
.scan(spin.initialState, spin.reducer)
.handleEvents(receiveOutput: currentState.send)
.eraseToAnyPublisher()
}.eraseToAnyPublisher()
}

static func start<Event>(spin: Spin<Value, Event>) -> AnyCancellable {
static func start<Event, Executer>(spin: ScheduledSpin<Value, Event, Executer>) -> AnyCancellable
where Executer: ExecuterDefinition, Executer.Executer: Scheduler {
AnyPublisher.stream(from: spin).consume()
}
}
3 changes: 3 additions & 0 deletions Sources/Combine/AnyScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
//

import Combine
import Dispatch
import Foundation
import SpinCommon

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public extension Scheduler {
Expand Down
17 changes: 17 additions & 0 deletions Sources/Combine/DispatchQueue+Executer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// DispatchQueue+Executer.swift
//
//
// Created by Thibault Wittemberg on 2020-08-04.
//

import Dispatch
import Foundation
import SpinCommon

extension DispatchQueue: ExecuterDefinition {
public typealias Executer = DispatchQueue
public static func defaultSpinExecuter() -> Executer {
DispatchQueue(label: "io.warpfactor.spin.dispatch-queue.\(UUID())")
}
}
8 changes: 4 additions & 4 deletions Sources/Combine/Feedback.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public typealias ScheduledCombineFeedback = SpinCombine.ScheduledFeedback
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias CombineFeedback = SpinCombine.Feedback

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias Feedback<State, Event> =
ScheduledFeedback<State, Event, DispatchQueue.SchedulerTimeType, DispatchQueue.SchedulerOptions>

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public struct ScheduledFeedback<State, Event, SchedulerTime, SchedulerOptions>: FeedbackDefinition
where SchedulerTime: Strideable, SchedulerTime.Stride: SchedulerTimeIntervalConvertible {
Expand Down Expand Up @@ -81,7 +85,3 @@ where SchedulerTime: Strideable, SchedulerTime.Stride: SchedulerTimeIntervalConv
self.init(effect: effect, on: executer)
}
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias Feedback<State, Event> =
ScheduledFeedback<State, Event, DispatchQueue.SchedulerTimeType, DispatchQueue.SchedulerOptions>
19 changes: 19 additions & 0 deletions Sources/Combine/OperationQueue+Executer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// OperationQueue+Executer.swift
//
//
// Created by Thibault Wittemberg on 2020-08-04.
//

import Foundation
import SpinCommon

extension OperationQueue: ExecuterDefinition {
public typealias Executer = OperationQueue
public static func defaultSpinExecuter() -> Executer {
let queue = OperationQueue()
queue.name = "io.warpfactor.spin.operationqueue.\(UUID())"
queue.maxConcurrentOperationCount = 1
return queue
}
}
53 changes: 0 additions & 53 deletions Sources/Combine/Reducer.swift

This file was deleted.

16 changes: 16 additions & 0 deletions Sources/Combine/RunLoop+Executer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// RunLoop+Executer.swift
//
//
// Created by Thibault Wittemberg on 2020-08-04.
//

import Foundation
import SpinCommon

extension RunLoop: ExecuterDefinition {
public typealias Executer = RunLoop
public static func defaultSpinExecuter() -> Executer {
RunLoop.main
}
}
14 changes: 13 additions & 1 deletion Sources/Combine/Spin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@
//

import Combine
import Dispatch
import Foundation
import SpinCommon

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias Spin<State, Event> = AnySpin<AnyPublisher<State, Never>, AnyPublisher<Event, Never>>
public typealias ScheduledSpin<State, Event, Executer> = AnySpin<AnyPublisher<State, Never>, AnyPublisher<Event, Never>, Executer>
where Executer: ExecuterDefinition, Executer.Executer: Scheduler

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias Spin<State, Event> = ScheduledSpin<State, Event, DispatchQueue>

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias RunLoopSpin<State, Event> = ScheduledSpin<State, Event, RunLoop>

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias OperationQueueSpin<State, Event> = ScheduledSpin<State, Event, OperationQueue>

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias CombineSpin = SpinCombine.Spin
22 changes: 22 additions & 0 deletions Sources/Combine/Spinner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Spinner.swift
//
//
// Created by Thibault Wittemberg on 2020-08-04.
//

import Dispatch
import Foundation
import SpinCommon

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias Spinner<State> = AnySpinner<State, DispatchQueue>

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias RunLoopSpinner<State> = AnySpinner<State, RunLoop>

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias OperationQueueSpinner<State> = AnySpinner<State, OperationQueue>

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias CombineSpinner = SpinCombine.Spinner
Loading

0 comments on commit 00f447c

Please sign in to comment.