Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idea: Allow EffectRouter to be passed to Mobius.loop directly #114

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion MobiusCore/Source/Connectable.swift
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ import Foundation
///
/// Alternatively used in `MobiusController.connectView(_:)` to connect a view to the controller. In that case, the
/// incoming values will be models, and the outgoing values will be events.
public protocol Connectable {
public protocol Connectable: _EffectHandlerConvertible {
associatedtype Input
associatedtype Output

@@ -44,6 +44,12 @@ public protocol Connectable {
func connect(_ consumer: @escaping Consumer<Output>) -> Connection<Input>
}

public extension Connectable {
func _asEffectHandlerConnectable() -> AnyConnectable<Input, Output> {
return AnyConnectable(self)
}
}

/// Type-erased wrapper for `Connectable`s
public final class AnyConnectable<Input, Output>: Connectable {
private let connectClosure: (@escaping Consumer<Output>) -> Connection<Input>
6 changes: 6 additions & 0 deletions MobiusCore/Source/EffectHandlers/EffectRouter.swift
Original file line number Diff line number Diff line change
@@ -72,6 +72,12 @@ public struct EffectRouter<Effect, Event> {
}
}

extension EffectRouter: _EffectHandlerConvertible {
public func _asEffectHandlerConnectable() -> AnyConnectable<Effect, Event> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the _asEffectHandlerConnectable() methods be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes; this is necessary since protocols can’t have multiple levels of access control. In order to publicly conform to the protocol the method must be implemented publicly.

return compose(routes: routes)
}
}

public struct _PartialEffectRouter<Effect, Payload, Event> {
fileprivate let routes: [Route<Effect, Event>]
fileprivate let path: (Effect) -> Payload?
34 changes: 23 additions & 11 deletions MobiusCore/Source/Mobius.swift
Original file line number Diff line number Diff line change
@@ -54,15 +54,16 @@ public extension Mobius {
///
/// - Parameters:
/// - update: the `Update` function of the loop
/// - effectHandler: an instance conforming to `Connectable`. Will be used to handle effects by the loop.
/// - effectHandler: the entity that will be used by the loop to handle effects. May be an `EffectRouter`, or any
/// instance conforming to `Connectable` with `Effect` as input and `Event` as output.
/// - Returns: a `Builder` instance that you can further configure before starting the loop
static func loop<Model, Event, Effect, EffectHandler: Connectable>(
static func loop<Model, Event, Effect, EffectHandler: _EffectHandlerConvertible>(
update: Update<Model, Event, Effect>,
effectHandler: EffectHandler
) -> Builder<Model, Event, Effect> where EffectHandler.Input == Effect, EffectHandler.Output == Event {
) -> Builder<Model, Event, Effect> where EffectHandler._EHEffect == Effect, EffectHandler._EHEvent == Event {
return Builder(
update: update,
effectHandler: effectHandler,
effectHandler: effectHandler._asEffectHandlerConnectable(),
initiate: nil,
eventSource: AnyEventSource({ _ in AnonymousDisposable(disposer: {}) }),
eventConsumerTransformer: { $0 },
@@ -74,12 +75,13 @@ public extension Mobius {
///
/// - Parameters:
/// - update: the update function of the loop
/// - effectHandler: an instance conforming to `Connectable`. Will be used to handle effects by the loop.
/// - effectHandler: the entity that will be used by the loop to handle effects. May be an `EffectRouter`, or any
/// instance conforming to `Connectable` with `Effect` as input and `Event` as output.
/// - Returns: a `Builder` instance that you can further configure before starting the loop
static func loop<Model, Event, Effect, EffectHandler: Connectable>(
static func loop<Model, Event, Effect, EffectHandler: _EffectHandlerConvertible>(
update: @escaping (Model, Event) -> Next<Model, Effect>,
effectHandler: EffectHandler
) -> Builder<Model, Event, Effect> where EffectHandler.Input == Effect, EffectHandler.Output == Event {
) -> Builder<Model, Event, Effect> where EffectHandler._EHEffect == Effect, EffectHandler._EHEvent == Event {
return self.loop(
update: Update(update),
effectHandler: effectHandler
@@ -94,16 +96,16 @@ public extension Mobius {
private let logger: AnyMobiusLogger<Model, Event, Effect>
private let eventConsumerTransformer: ConsumerTransformer<Event>

fileprivate init<EffectHandler: Connectable>(
fileprivate init(
update: Update<Model, Event, Effect>,
effectHandler: EffectHandler,
effectHandler: AnyConnectable<Effect, Event>,
initiate: Initiate<Model, Effect>?,
eventSource: AnyEventSource<Event>,
eventConsumerTransformer: @escaping ConsumerTransformer<Event>,
logger: AnyMobiusLogger<Model, Event, Effect>
) where EffectHandler.Input == Effect, EffectHandler.Output == Event {
) {
self.update = update
self.effectHandler = AnyConnectable(effectHandler)
self.effectHandler = effectHandler
self.initiate = initiate
self.eventSource = eventSource
self.logger = logger
@@ -234,3 +236,13 @@ public extension Mobius {
}
}
}

/// Implementation detail, do not use.
///
/// This protocol is used for parameters that may be either an `EffectRouter` or a `Connectable`.
public protocol _EffectHandlerConvertible {
associatedtype _EHEffect
associatedtype _EHEvent

func _asEffectHandlerConnectable() -> AnyConnectable<_EHEffect, _EHEvent>
}
1 change: 0 additions & 1 deletion MobiusCore/Test/MobiusLoopTests.swift
Original file line number Diff line number Diff line change
@@ -290,7 +290,6 @@ class MobiusLoopTests: QuickSpec {
let payload: (Int) -> Int? = { $0 }
let effectConnectable = EffectRouter<Int, Int>()
.routeEffects(withPayload: payload).to(effectHandler)
.asConnectable
let update = Update { (_: Int, _: Int) -> Next<Int, Int> in Next.dispatchEffects([1]) }
loop = Mobius
.loop(update: update, effectHandler: effectConnectable)
5 changes: 2 additions & 3 deletions MobiusCore/Test/NonReentrancyTests.swift
Original file line number Diff line number Diff line change
@@ -75,11 +75,10 @@ class NonReentrancyTests: QuickSpec {
return AnonymousDisposable {}
}

let effectConnectable = EffectRouter<Effect, Event>()
let effectRouter = EffectRouter<Effect, Event>()
.routeEffects(equalTo: .testEffect).to(testEffectHandler)
.asConnectable

loop = Mobius.loop(update: update, effectHandler: effectConnectable)
loop = Mobius.loop(update: update, effectHandler: effectRouter)
.start(from: 0)
}