Skip to content
Open
Show file tree
Hide file tree
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
Expand Up @@ -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

Expand All @@ -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>
Expand Down
6 changes: 6 additions & 0 deletions MobiusCore/Source/EffectHandlers/EffectRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
34 changes: 23 additions & 11 deletions MobiusCore/Source/Mobius.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Up @@ -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)
Expand Down
5 changes: 2 additions & 3 deletions MobiusCore/Test/NonReentrancyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down