diff --git a/MobiusCore/Source/Connectable.swift b/MobiusCore/Source/Connectable.swift index 7c0c7e19..fc50f1bf 100644 --- a/MobiusCore/Source/Connectable.swift +++ b/MobiusCore/Source/Connectable.swift @@ -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) -> Connection } +public extension Connectable { + func _asEffectHandlerConnectable() -> AnyConnectable { + return AnyConnectable(self) + } +} + /// Type-erased wrapper for `Connectable`s public final class AnyConnectable: Connectable { private let connectClosure: (@escaping Consumer) -> Connection diff --git a/MobiusCore/Source/EffectHandlers/EffectRouter.swift b/MobiusCore/Source/EffectHandlers/EffectRouter.swift index 38902d66..97adc204 100644 --- a/MobiusCore/Source/EffectHandlers/EffectRouter.swift +++ b/MobiusCore/Source/EffectHandlers/EffectRouter.swift @@ -72,6 +72,12 @@ public struct EffectRouter { } } +extension EffectRouter: _EffectHandlerConvertible { + public func _asEffectHandlerConnectable() -> AnyConnectable { + return compose(routes: routes) + } +} + public struct _PartialEffectRouter { fileprivate let routes: [Route] fileprivate let path: (Effect) -> Payload? diff --git a/MobiusCore/Source/Mobius.swift b/MobiusCore/Source/Mobius.swift index 92bab342..b7a8c188 100644 --- a/MobiusCore/Source/Mobius.swift +++ b/MobiusCore/Source/Mobius.swift @@ -72,15 +72,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( + static func loop( update: Update, effectHandler: EffectHandler - ) -> Builder where EffectHandler.Input == Effect, EffectHandler.Output == Event { + ) -> Builder 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 }, @@ -92,12 +93,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( + static func loop( update: @escaping (Model, Event) -> Next, effectHandler: EffectHandler - ) -> Builder where EffectHandler.Input == Effect, EffectHandler.Output == Event { + ) -> Builder where EffectHandler._EHEffect == Effect, EffectHandler._EHEvent == Event { return self.loop( update: Update(update), effectHandler: effectHandler @@ -117,16 +119,16 @@ public extension Mobius { private let logger: AnyMobiusLogger private let eventConsumerTransformer: ConsumerTransformer - fileprivate init( + fileprivate init( update: Update, - effectHandler: EffectHandler, + effectHandler: AnyConnectable, initiate: Initiate?, eventSource: AnyEventSource, eventConsumerTransformer: @escaping ConsumerTransformer, logger: AnyMobiusLogger - ) 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 @@ -279,3 +281,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> +} diff --git a/MobiusCore/Test/MobiusLoopTests.swift b/MobiusCore/Test/MobiusLoopTests.swift index 1a9121ed..75470c31 100644 --- a/MobiusCore/Test/MobiusLoopTests.swift +++ b/MobiusCore/Test/MobiusLoopTests.swift @@ -290,7 +290,6 @@ class MobiusLoopTests: QuickSpec { let payload: (Int) -> Int? = { $0 } let effectConnectable = EffectRouter() .routeEffects(withPayload: payload).to(effectHandler) - .asConnectable let update = Update { (_: Int, _: Int) -> Next in Next.dispatchEffects([1]) } loop = Mobius .loop(update: update, effectHandler: effectConnectable) diff --git a/MobiusCore/Test/NonReentrancyTests.swift b/MobiusCore/Test/NonReentrancyTests.swift index 73dd3484..16c9e3e7 100644 --- a/MobiusCore/Test/NonReentrancyTests.swift +++ b/MobiusCore/Test/NonReentrancyTests.swift @@ -75,11 +75,10 @@ class NonReentrancyTests: QuickSpec { return AnonymousDisposable {} } - let effectConnectable = EffectRouter() + let effectRouter = EffectRouter() .routeEffects(equalTo: .testEffect).to(testEffectHandler) - .asConnectable - loop = Mobius.loop(update: update, effectHandler: effectConnectable) + loop = Mobius.loop(update: update, effectHandler: effectRouter) .start(from: 0) }