-
Notifications
You must be signed in to change notification settings - Fork 42
Using MobiusController
In earlier parts of the documentation, we’ve been using “raw” Mobius loops. A raw loop is single-threaded, and keeps its effect handler and event source connections open until it’s disposed of. MobiusController
adds a layer of sophistication on top of this, running a loop on a background queue and adding start-stop semantics.
Running a loop on a background queue isn’t usually a great performance win, but it actually simplifies matters when dealing with asynchronous effect handlers. In real-world code, it is common for effect handlers to interact with APIs that use completion handlers, which generally won’t all fire on the same queue. If you use a raw MobiusLoop
, you are responsible for avoiding concurrent access to the loop. When you use a MobiusController
, events can be dispatched from arbitrary threads and will automatically be handled on one queue managed by the controller.
A MobiusController
is created by calling makeController
on a Mobius.Builder
, instead of start
:
let controller = Mobius.loop(update: myUpdate, effectHandler: myEffectHandler)
.withEventSource(myEventSource)
.withLogger(SimpleLogger(tag: "my loop"))
.makeController(from: model, initiate: myInitiate)
// MobiusController starts out in a stopped state
controller.start()
Unlike a raw loop, a Mobius controller can be stopped and restarted. For example, when a Mobius controller is associated with a UIViewController
, it makes sense to start it in viewWillAppear
and stop it in viewDidDisappear
to avoid unnecessary state management when the view is hidden.
When the controller is stopped, it disconnects from the effect handler and event source, and any events that have already been queued up will be ignored.
When the controller is restarted, there is often a need to resynchronize state with collaborator objects, or to back the model out of a temporary state. This is done using an initiator function which is passed to makeController
.
With raw loops, the model can be observed through simple functions registered with addObserver
, which normally stay registered for the lifetime of the loop.
In order to handle the start-stop behaviour of MobiusController
, a more complex solution is used, the view connectable. A view connectable implements a connect
function, which takes an event consumer function and returns a connection. The connection’s acceptClosure
will be called with every new model, and its disposeClosure
will be called when the loop is stopped.
The view connectable can only be set (or removed) when the controller is stopped.
let controller = Mobius.loop(update: myUpdate, effectHandler: myEffectHandler)
.makeController(from: model, initiate: myInitiate)
controller.connectView(myViewConnectable)
controller.start()
We recommend defaulting to MobiusController
rather than raw loops. However, raw loops have one potential advantage: they operate synchronously. Calling dispatchEvent
on a raw loop will immediately:
- Call
update
to handle the provided event - Update the model
- Call the effect handler to handle any effects issued by the
update
call - Handle any events issued by the effect handler before it returns. (The effect handler may asynchronously dispatch effects later; we don’t wait for those.)
This means that you can implement synchronous interfaces using a MobiusLoop
by calling dispatchEvent
immediately followed by reading latestModel
. Since MobiusController
does everything asynchronously, there is no simple equivalent.
Getting Started
Reference Guide
Patterns