### Create and Start MobiusController Source: https://github.com/spotify/mobius.swift/wiki/Using-MobiusController Instantiate a MobiusController using makeController and start its execution. The controller manages event dispatching on a background queue. ```swift 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() ``` -------------------------------- ### Create and Start a Beginner Mobius Loop Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Initialize a Mobius loop using the `beginnerLoop` function with the defined update function and start it with an initial state. Ensure MobiusCore and MobiusExtras are imported. ```swift import MobiusCore import MobiusExtras let loop = Mobius.beginnerLoop(update: update) .start(from: 2) ``` -------------------------------- ### Create and Start a Mobius Loop Source: https://github.com/spotify/mobius.swift/wiki/Mobius-Loop This snippet shows the standard way to initialize a Mobius loop with an update function and an effect handler, and then start it with an initial model. The loop manages state changes and effect dispatching. ```swift loop = Mobius.loop(update: update, effectHandler: effectHandler) .start(from: Model.default) ``` -------------------------------- ### Swift Package Manager Installation Source: https://github.com/spotify/mobius.swift/blob/master/README.md Add this entry to your Package.swift file to include Mobius.swift in your project. ```swift .package(url: "https://github.com/spotify/Mobius.swift", from: "0.5.0") ``` -------------------------------- ### Create Mobius Loop Builder Without Starting Source: https://github.com/spotify/mobius.swift/wiki/Configuring-a-MobiusLoop Obtain a Mobius loop builder without immediately starting the loop. This allows for further configuration before the loop is initiated. ```swift let loopBuilder: Mobius.Builder = Mobius.loop(update: update, effectHandler: effectHandler) ``` -------------------------------- ### Start Mobius Loop with Effects Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Initializes and starts a Mobius loop using the defined `update` function and `effectHandler`. The loop starts with an initial model value of 2. ```swift let loop: Mobius.loop(update: update, effectHandler: effectHandler) .start(from: 2) loop.addObserver { counter in print(counter) } ``` -------------------------------- ### Create Effect Handler Source: https://github.com/spotify/mobius.swift/blob/master/README.md Set up an EffectRouter to handle specific effects. This example routes the playSound effect to a beep function. ```swift import AVFoundation private func beep() { AudioServicesPlayAlertSound(SystemSoundID(1322)) } let effectHandler = EffectRouter() .routeCase(CounterEffect.playSound).to { beep() } .asConnectable ``` -------------------------------- ### Configure Event Source with Mobius Builder Source: https://github.com/spotify/mobius.swift/wiki/Event-Source Use `withEventSource` on a Mobius.Builder to integrate an Event Source into your loop. This is typically done after creating the loop and before starting it. ```swift let loop = Mobius.loop(update: update, effectHandler: EffectHandler()) .withEventSource(myEventSource) .start(from: .empty) ``` -------------------------------- ### Start Mobius Loop with Mandatory Parameters Source: https://github.com/spotify/mobius.swift/wiki/Configuring-a-MobiusLoop Initiates a Mobius loop by providing only the essential update function and effect handler, using default values for other parameters. ```swift let loop = Mobius.loop(update: update, effectHandler: effectHandler) .start(from: 2) ``` -------------------------------- ### Initialize Mobius Loop Source: https://github.com/spotify/mobius.swift/blob/master/README.md Tie together the update function and effect handler to create the Mobius application loop, starting with an initial model state. ```swift let application = Mobius.loop(update: update, effectHandler: effectHandler) .start(from: 0) ``` -------------------------------- ### Mobius Initiate Function Signature Source: https://github.com/spotify/mobius.swift/wiki/Concepts The Initiate function is used to start or resume a Mobius loop. It takes the current model and returns a First object containing a model and effects to run. This is useful for initial data loading or retrying failed operations. ```swift (Model) -> First ``` -------------------------------- ### Create Mobius Loop Builder in Swift Source: https://github.com/spotify/mobius.swift/wiki/Mobius-and-Mobile-Apps Use Mobius.Builder to create a reusable Mobius loop configuration. This is useful for starting loops from different states or multiple times. ```swift let loopBuilder: Mobius.Builder = Mobius .loop(update: myUpdate, effectHandler: myEffectHandler) .withInitiator(myInit) .withEventSource(myEventSource) .logger(ConsoleLogger(tag: "my_app")) ``` -------------------------------- ### Configure Mobius Loop with Optional Parameters Source: https://github.com/spotify/mobius.swift/wiki/Configuring-a-MobiusLoop Configures a Mobius loop by adding an event source, a logger, and an event consumer transformer before starting the loop with an initial model. ```swift let loop = Mobius.loop(update: update, effectHandler: effectHandler) .withEventSource(eventSource) .withLogger(SimpleLogger(tag: "my loop")) // SimpleLogger can be found in MobiusExtras .withEventConsumerTransformer { $0 } .start(from: model) ``` -------------------------------- ### Configure Mobius Loop with Builder Source: https://context7.com/spotify/mobius.swift/llms.txt Use Mobius.Builder to configure a loop with an update function, effect handler, logger, event source, and initial state. The loop can then be started or used to create a controller. ```swift import MobiusCore import MobiusExtras // --- Types --- typealias SearchModel = String // the current query enum SearchEvent { case queryChanged(String); case resultsReceived([String]) } enum SearchEffect: Equatable { case search(query: String) } // --- Update --- func searchUpdate(model: SearchModel, event: SearchEvent) -> Next { switch event { case .queryChanged(let query): return .next(query, effects: query.isEmpty ? [] : [.search(query: query)]) case .resultsReceived: return .noChange // handled by UI observer } } // --- Effect Handler --- let effectHandler = EffectRouter() .routeCase(SearchEffect.search) .to { query, callback in // Simulate async network search DispatchQueue.global().asyncAfter(deadline: .now() + 0.3) { let fakeResults = ["Result for \(query)"] callback.end(with: .resultsReceived(fakeResults)) } return AnonymousDisposable { /* cancel request */ } } .asConnectable // --- Full builder configuration --- let loop = Mobius.loop(update: searchUpdate, effectHandler: effectHandler) .withLogger(SimpleLogger(tag: "Search")) // logs every event/model/effect .withEventSource(NetworkEventSource()) // external connectivity events .start(from: "") loop.addObserver { query in print("Current query:", query) } loop.dispatchEvent(.queryChanged("mobius")) // Logs: // Search: Event received: queryChanged("mobius") // Search: Model updated: "mobius" // Search: Effect dispatched: search(query: "mobius") loop.dispose() ``` -------------------------------- ### Connect MobiusLoop.Controller to UIViewController Lifecycle Source: https://github.com/spotify/mobius.swift/wiki/Mobius-and-Mobile-Apps Integrate the MobiusLoop.Controller with a UIViewController's lifecycle methods. Connect the view in `viewWillAppear` and start the loop, then stop and disconnect in `viewWillDisappear`. ```swift class MyViewController: UIViewController { private let loopController: MobiusController // ... override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) loopController.connectView(AnyConnectable(self.connectViews)) loopController.start() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) loopController.stop() loopController.disconnectView() } //... } ``` -------------------------------- ### Define Counter Model Type Source: https://github.com/spotify/mobius.swift/blob/master/README.md Define the type alias for the counter model, which is a simple integer in this example. ```swift typealias CounterModel = Int ``` -------------------------------- ### Implement Update Function Logic in Swift Source: https://github.com/spotify/mobius.swift/wiki/The-Mobius-Workflow Implement the logic within your update function to handle events and produce effects based on the current model state. This example shows how to conditionally dispatch an effect when a specific event occurs and the model meets certain criteria. ```swift // ... case .loginButtonClicked: if !model.online { return .dispatchEffects([.showErrorToast(message:"must be online to log in")]) } // ... ``` -------------------------------- ### Observe Model Changes in a Mobius Loop Source: https://github.com/spotify/mobius.swift/wiki/Mobius-Loop Add an observer to a Mobius loop to be notified whenever the Model changes. The returned Disposable can be used to stop observing. The loop will start even without an observer. ```swift let disposable = loop.addObserver(onModelChanged) ``` -------------------------------- ### Define Counter Effects with Associated String Value Source: https://github.com/spotify/mobius.swift/wiki/Defining-Events-and-Effects Use Swift enumerations with associated values to define effects that require additional data. This example shows an effect that carries a reason string. ```swift enum CounterEffects { case reportFailure(reason: String) } ``` -------------------------------- ### Create Effect-Free Loop with Mobius.beginnerLoop Source: https://context7.com/spotify/mobius.swift/llms.txt Use `Mobius.beginnerLoop` for simple state management without side effects. It accepts a plain `(Model, Event) -> Model` update function. Always dispose of the loop when finished. ```swift import MobiusCore import MobiusExtras enum CountEvent { case increment, decrement } // Plain model -> model update, no effects needed func counterUpdate(model: Int, event: CountEvent) -> Int { switch event { case .increment: return model + 1 case .decrement: return max(0, model - 1) } } let loop = Mobius.beginnerLoop(update: counterUpdate) .start(from: 0) loop.addObserver { print("Counter:", $0) } // immediately prints "Counter: 0" loop.dispatchEvent(.increment) // Counter: 1 loop.dispatchEvent(.increment) // Counter: 2 loop.dispatchEvent(.decrement) // Counter: 1 loop.dispatchEvent(.decrement) // Counter: 0 loop.dispatchEvent(.decrement) // Counter: 0 (clamped at zero) loop.dispose() // always clean up ``` -------------------------------- ### Test Initiate Function with InitSpec Source: https://context7.com/spotify/mobius.swift/llms.txt Use InitSpec to test the Initiate function in isolation with a when/then pattern. Requires MobiusCore and MobiusTest. ```swift import XCTest import MobiusCore import MobiusTest class LoginInitiateTests: XCTestCase { let spec = InitSpec(loginInitiate) func testLoadingStateRetriggersLogin() { let loadingModel = LoginModel(email: "a@b.com", password: "secret", isLoading: true) spec .when(loadingModel) .then(assertThatFirst( hasModel(LoginModel(email: "a@b.com", password: "secret", isLoading: false)), hasEffects([.attemptLogin(email: "a@b.com", password: "secret")]) )) } func testIdleStateStartsClean() { let idleModel = LoginModel(email: "", password: "", isLoading: false) spec .when(idleModel) .then(assertThatFirst( hasModel(idleModel), hasNoEffects() )) } } ``` -------------------------------- ### Dispatch Events and Observe Output Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Demonstrates dispatching events to the Mobius loop and shows the expected output, including model changes and side-effect execution (printing 'error!'). ```swift loop.dispatchEvent(.down) // prints "1" loop.dispatchEvent(.down) // prints "0" loop.dispatchEvent(.down) // prints "0", followed by "error!" loop.dispatchEvent(.up) // prints "1" loop.dispatchEvent(.up) // prints "2" loop.dispatchEvent(.down) // prints "1" ``` -------------------------------- ### Initialize MobiusLoop.Controller in Swift Source: https://github.com/spotify/mobius.swift/wiki/Mobius-and-Mobile-Apps Initialize a MobiusLoop.Controller with a builder and a default model. This controller manages the loop's lifecycle, allowing it to be stopped and resumed. ```swift let controller = MobiusController(builder: loopBuilder, defaultModel: MyModel.makeDefault()); ``` -------------------------------- ### Configure EffectRouter with Effect Cases Source: https://github.com/spotify/mobius.swift/wiki/Effect-Handler Set up an EffectRouter to route different Effect cases to specific actions or event emissions. Requires effects to be Equatable for case routing. ```swift let effectHandler = EffectRouter() .routeCase(MyEffect.closeApplication) .to { closeApplication() } .routeCase(MyEffect.stopPlayback) .toEvent { player.stopPlayback() return MyEvent.playbackStopped } .routeCase(MyEffect.fetchUsersWithIDs) .to(UserFetcher(dataSource: dataSource)) // defined below .asConnectable ``` -------------------------------- ### Testing Effect Handlers as Functions Source: https://github.com/spotify/mobius.swift/wiki/Effect-Handler Illustrates a common testing pattern for effect handlers that produce events. This approach treats the handler as a pure function from effects to events, suitable for given-when-then style tests. ```text given: My effect handler when: It receives some effect, A then: Expect some event B to be emitted eventually. ``` -------------------------------- ### Next Factory Methods for Effects Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Illustrates the four static factory methods of `Next` for controlling model updates and effect dispatching. These methods provide precise control over loop behavior. ```swift | | Model changed | Model unchanged | |----------------|---------------------------|-------------------------------| | **Effects** | Next.next(model, effects) | Next.dispatchEffects(effects) | | **No Effects** | Next.next(model) | Next.noChange | ``` -------------------------------- ### Implement View Connection and Event Handling in Swift Source: https://github.com/spotify/mobius.swift/wiki/Mobius-and-Mobile-Apps Implement the `connectViews` method to handle UI updates based on the model and to consume UI events. This method sets up event listeners and disposes of them when the connection is closed. ```swift private var eventConsumer: Consumer? // ... @objc func emitEvent() { eventConsumer?(MyEvent.buttonPressed) } func connectViews(consumer: @escaping Consumer) -> Connection { self.eventConsumer = consumer button.addTarget(self, action: #selector(emitEvent), for: .touchUpInside) let accept: (MyModel) -> Void = { model in // Keep in mind that this closure can be called from a background thread DispatchQueue.main.async { self.myLabel.text = model.getText() } } let dispose = { self.eventConsumer = nil self.button.removeTarget(nil, action: nil, for: .allEvents) } return Connection(acceptClosure: accept, disposeClosure: dispose) } ``` -------------------------------- ### Nimble-Integrated Matchers with MobiusNimble Source: https://context7.com/spotify/mobius.swift/llms.txt Utilize MobiusNimble for Nimble-flavoured matchers in Quick/Nimble test suites. Integrates with Quick, Nimble, MobiusCore, MobiusTest, and MobiusNimble. ```swift import Quick import Nimble import MobiusCore import MobiusTest import MobiusNimble class LoginSpec: QuickSpec { override func spec() { let spec = UpdateSpec(loginUpdate) describe("loginUpdate") { context("when login is tapped with valid credentials") { it("sets isLoading and dispatches attemptLogin") { let model = LoginModel(email: "a@b.com", password: "pass", isLoading: false) spec.given(model).when(.loginTapped).then( assertThatNext( haveModel(LoginModel(email: "a@b.com", password: "pass", isLoading: true)), haveEffects([.attemptLogin(email: "a@b.com", password: "pass")]) ) ) } } context("when login succeeds") { it("dispatches only navigateHome with no model change") { let model = LoginModel(email: "a@b.com", password: "pass", isLoading: true) spec.given(model).when(.loginSucceeded).then( assertThatNext( haveNoModel(), haveOnlyEffects([.navigateHome]) ) ) } } } } } ``` -------------------------------- ### Dispatching Events to a Mobius Loop Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Demonstrates how to send events to a Mobius loop instance. These events are processed by the `update` function, potentially triggering effects. ```swift loop.dispatchEvent(.down) // prints "1" loop.dispatchEvent(.down) // prints "0" loop.dispatchEvent(.down) // prints "error!" loop.dispatchEvent(.up) // prints "1" loop.dispatchEvent(.up) // prints "2" loop.dispatchEvent(.down) // prints "1" ``` -------------------------------- ### Add Mobius.swift Dependency to Package.swift Source: https://context7.com/spotify/mobius.swift/llms.txt Declare Mobius.swift targets in your Package.swift file to include the necessary Mobius components in your project. ```swift // Package.swift dependencies: [ .package(url: "https://github.com/spotify/Mobius.swift", from: "0.5.0") ], targets: [ .target( name: "MyApp", dependencies: [ .product(name: "MobiusCore", package: "Mobius.swift"), .product(name: "MobiusExtras", package: "Mobius.swift"), ] ), .testTarget( name: "MyAppTests", dependencies: [ .product(name: "MobiusTest", package: "Mobius.swift"), .product(name: "MobiusNimble", package: "Mobius.swift"), ] ), ] ``` -------------------------------- ### Initial Update Function for Counter Source: https://github.com/spotify/mobius.swift/blob/master/README.md This function defines how the model changes in response to events, without considering side effects. ```swift func update(model: CounterModel, event: CounterEvent) -> CounterModel { switch event { case .increment: return model + 1 case .decrement: return model - 1 } } ``` -------------------------------- ### Implement EffectHandler Protocol Source: https://github.com/spotify/mobius.swift/wiki/Effect-Handler Implement the EffectHandler protocol to define how a specific effect is handled. This struct handles fetching users and ends with a usersFetched event. ```swift struct UserFetcher: EffectHandler { let dataSource: DataSource func handle( _ userIDs: [String], _ callback: EffectCallback ) -> Disposable { let request = dataSource .downloadUsers(ids: userIDs) .then { users in callback.end(with: .usersFetched(users)) } return AnonymousDisposable { request.cancel() } } } ``` -------------------------------- ### Define MyEvent and MyEffect Enums Source: https://github.com/spotify/mobius.swift/wiki/Effect-Handler Define the events and effects for your Mobius loop. Ensure effects that need to be routed by case conform to Equatable. ```swift enum MyEvent { case playbackStopped case usersFetched([User]) } enum MyEffect: Equatable { case closeApplication case stopPlayback case fetchUsers(ids: [String]) } ``` -------------------------------- ### Built-in Debug Logger with SimpleLogger Source: https://context7.com/spotify/mobius.swift/llms.txt Use SimpleLogger from MobiusExtras to log events, model updates, and dispatched effects. Attach via .withLogger(...) on the builder. Supports custom consumers. ```swift import MobiusCore import MobiusExtras // Default: prints to stdout with tag prefix let loop = Mobius.loop(update: loginUpdate, effectHandler: myEffectHandler) .withLogger(SimpleLogger(tag: "Login")) .start(from: LoginModel(email: "", password: "", isLoading: false)) loop.dispatchEvent(.emailChanged("user@example.com")) // Login: Event received: emailChanged("user@example.com") // Login: Model updated: LoginModel(email: "user@example.com", password: "", isLoading: false) // Custom consumer (e.g., route to OSLog or analytics): let osLogger = SimpleLogger(tag: "Login") { message in os_log("%{public}s", message) } ``` -------------------------------- ### Struct for All States with Flags Source: https://github.com/spotify/mobius.swift/wiki/Defining-Models This struct approach is suitable for models with many overlapping states or when frequent modifications are expected. It uses flags and optional properties to represent the state, offering flexibility but requiring careful handling of invalid combinations. ```swift struct Model: Copyable { var loaded: Bool var error: Bool var offline: Bool var data: String? var errorMessage: String? } ``` -------------------------------- ### Configure Effect Router Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Sets up an `EffectRouter` to map the `MyEffect.reportErrorNegative` case to the `handleReportErrorNegative` function. The `asConnectable` property converts the router to the required `Connectable` type. ```swift let effectHandler = EffectRouter() .routeCase(MyEffect.reportErrorNegative).to(handleReportErrorNegative) .asConnectable ``` -------------------------------- ### Using Copyable for Mutable Models in Swift Source: https://github.com/spotify/mobius.swift/wiki/Value-Semantics Demonstrates how to use the `Copyable` protocol to safely modify mutable struct properties within an update function. The closure receives an inout copy of the model, allowing localized changes. ```swift struct MyModel: Copyable { var label: String var value: Int } func update(model: MyModel, event: MyEvent) -> Next { // Note that model, being an argument, is immutable switch event { case .increment: return .next(model.copy { // $0 is an inout MyModel, which starts as a copy of `model` $0.value += 1 }) ... } } ``` -------------------------------- ### Dispatch Events to Mobius Loop Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Send events to the Mobius loop to trigger state transitions. The observer will print the new state after each event. ```swift loop.dispatchEvent(.down) // prints "1" loop.dispatchEvent(.down) // prints "0" loop.dispatchEvent(.down) // prints "0" loop.dispatchEvent(.up) // prints "1" loop.dispatchEvent(.up) // prints "2" loop.dispatchEvent(.down) // prints "1" ``` -------------------------------- ### Write Update Function Tests in Swift Source: https://github.com/spotify/mobius.swift/wiki/The-Mobius-Workflow Use UpdateSpec to write tests for your update function. This involves defining the initial model state, triggering an event, and asserting the expected effects. This approach ensures your update logic behaves as expected under various conditions. ```swift UpdateSpec(update) .given(MyModel(online: false)) .when(.loginButtonClicked) .then(assertThatNext(hasEffects([.showErrorToast(message: "must be online to log in")]))) ``` -------------------------------- ### Implement Update Function for Counter Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Create an update function that takes the current state (counter) and an event, returning the new state. This function prevents the counter from going below zero. ```swift func update(counter: Int, event: MyEvent) -> Int { switch event { case .up: return counter + 1 case .down: return counter > 0 ? counter - 1 : counter } } ``` -------------------------------- ### Use MobiusLoop for Synchronous Event Processing Source: https://context7.com/spotify/mobius.swift/llms.txt Employ MobiusLoop for scenarios requiring synchronous event processing. Events are processed on the calling thread, allowing immediate reading of the latest model. Use addObserver to subscribe to model changes and dispose to unsubscribe. ```swift import MobiusCore let loop = Mobius.loop(update: loginUpdate, effectHandler: myEffectHandler) .start(from: LoginModel(email: "", password: "", isLoading: false)) // addObserver returns a Disposable to unsubscribe selectively let subscription = loop.addObserver { model in print("Model changed:", model) } loop.dispatchEvent(.emailChanged("alice@example.com")) print(loop.latestModel.email) // "alice@example.com" — synchronous read is safe subscription.dispose() // stop receiving updates loop.dispose() // tear down loop and effect handler ``` -------------------------------- ### Integrate MobiusController with UIViewController Source: https://context7.com/spotify/mobius.swift/llms.txt Use MobiusController for seamless integration with UIViewController. It manages loop lifecycle, background processing, and UI updates on the main queue. Connect views while the controller is stopped and start/stop it with viewWillAppear/viewDidDisappear. ```swift import MobiusCore import MobiusExtras // Initiate function: called every time the controller starts/restarts func loginInitiate(model: LoginModel) -> First { // If the app resumes mid-login, retry immediately if model.isLoading { var reset = model reset.isLoading = false return First(model: reset, effects: [.attemptLogin(email: model.email, password: model.password)]) } return First(model: model) } let controller = Mobius.loop(update: loginUpdate, effectHandler: myEffectHandler) .withLogger(SimpleLogger(tag: "Login")) .makeController(from: LoginModel(email: "", password: "", isLoading: false), initiate: loginInitiate) // Connect a view (must be done while stopped) controller.connectView(myLoginViewConnectable) // In viewWillAppear: controller.start() // Dispatch events from UI actions: controllercontroller.dispatchEvent(.emailChanged("user@example.com")) controller.dispatchEvent(.loginTapped) // In viewDidDisappear: controller.stop() // Last model is preserved; next start() will call initiate() with it. // Read current model at any time: print(controller.model) // Replace starting model (while stopped): controller.replaceModel(LoginModel(email: "", password: "", isLoading: false)) ``` -------------------------------- ### Unit Test Update Functions with UpdateSpec Source: https://context7.com/spotify/mobius.swift/llms.txt Utilize UpdateSpec for BDD-style unit testing of pure Update functions using MobiusTest. This allows testing without running a full loop. Combine with assertThatNext and predicates like hasModel, hasEffects, and hasNoEffects. ```swift import XCTest import MobiusCore import MobiusTest class LoginUpdateTests: XCTestCase { let spec = UpdateSpec(loginUpdate) func testLoginTappedDispatchesEffect() { let model = LoginModel(email: "a@b.com", password: "secret", isLoading: false) spec .given(model) .when(.loginTapped) .then(assertThatNext( hasModel(LoginModel(email: "a@b.com", password: "secret", isLoading: true)), hasEffects([.attemptLogin(email: "a@b.com", password: "secret")]) )) } func testLoginSucceededNavigatesHome() { spec .given(LoginModel(email: "a@b.com", password: "secret", isLoading: true)) .when(.loginSucceeded) .then(assertThatNext( hasNoModel(), // model unchanged hasEffects([.navigateHome]) )) } func testEmailChangedUpdatesModel() { spec .given(LoginModel(email: "", password: "", isLoading: false)) .when(.emailChanged("new@email.com")) .then(assertThatNext( hasModel(LoginModel(email: "new@email.com", password: "", isLoading: false)), hasNoEffects() )) } } ``` -------------------------------- ### Dispatch Events to Mobius Loop Source: https://github.com/spotify/mobius.swift/blob/master/README.md Send events to the running Mobius loop to trigger state updates and side effects. ```swift application.dispatchEvent(.increment) // Model is now 1 application.dispatchEvent(.decrement) // Model is now 0 application.dispatchEvent(.decrement) // Sound effect plays! Model is still 0 ``` -------------------------------- ### Inline Effect Handling with EffectRouter Source: https://github.com/spotify/mobius.swift/wiki/Effect-Handler Inline effect handling logic directly within the EffectRouter configuration using a closure. This avoids the need for a separate EffectHandler struct for simple cases. ```swift let effectHandler = EffectRouter() .routeCase(Effect.closeApplication) .to { closeApplication() } .routeCase(MyEffect.stopPlayback) .toEvent { player.stopPlayback() return MyEvent.playbackStopped } .routeCase(MyEffect.fetchUsersWithIDs) .to { userIDs, callback in let request = dataSource .downloadUsers(ids: userIDs) .then { users in callback.end(with: .usersFetched(users)) } return AnonymousDisposable { request.cancel() } } .asConnectable ``` -------------------------------- ### Update Function without Effects Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Initial update function that returns `Next` but does not dispatch any effects. It handles basic model updates. ```swift func update(model: Int, event: MyEvent) -> Next { switch event { case .up: return .next(model + 1) case .down: return model > 0 ? .next(model - 1) : .next(model) } } ``` -------------------------------- ### Connect View to MobiusController Source: https://github.com/spotify/mobius.swift/wiki/Using-MobiusController Connect a view connectable to the MobiusController to observe model updates. The view connectable handles model updates and disconnection when the controller stops. ```swift let controller = Mobius.loop(update: myUpdate, effectHandler: myEffectHandler) .makeController(from: model, initiate: myInitiate) controller.connectView(myViewConnectable) controller.start() ``` -------------------------------- ### Hybrid Model Approach Source: https://github.com/spotify/mobius.swift/wiki/Defining-Models Combine enum and struct benefits for a flexible model that avoids invalid states. This approach uses an enum for core states and a struct to manage additional properties like 'offline' status. ```swift enum LoadingState { case waitingForData case loaded(data: String) case error(message: String) } struct Model { let offline: Bool let loadingState: LoadingState } ``` -------------------------------- ### Define Counter Effects Source: https://github.com/spotify/mobius.swift/blob/master/README.md Define an enum to represent the possible side effects, such as playing a sound. ```swift enum CounterEffect { case playSound } ``` -------------------------------- ### Define Event, Effect, and Model Types in Swift Source: https://github.com/spotify/mobius.swift/wiki/The-Mobius-Workflow Define the core types for your Mobius application: events that trigger state changes, effects that represent side effects, and the model that holds your application's state. Ensure these types accurately reflect the behavior you intend to model. ```swift enum MyEvent { // ... case loginButtonClicked // ... } enum MyEffect { // ... case showErrorToast(message: String) // ... } struct MyModel { // ... var online: Bool // ... } ``` -------------------------------- ### Define Simple Counter Events with Enum Source: https://github.com/spotify/mobius.swift/wiki/Defining-Events-and-Effects Use Swift enumerations without associated values for simple event types. This is suitable when no additional data is needed for an event. ```swift enum CounterEvents { case increment case decrement } ``` -------------------------------- ### Add Observer to Mobius Loop Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Add an observer to the Mobius loop to react to state changes. Observers are called immediately with the current state upon addition. ```swift loop.addObserver { counter in print(counter) } ``` -------------------------------- ### Define Counter Events Source: https://github.com/spotify/mobius.swift/wiki/Creating-a-loop Define an enum to represent the events that can be sent to the Mobius loop. These events will trigger state changes. ```swift enum MyEvent { case up case down } ``` -------------------------------- ### Mobius Update Function Signature Source: https://github.com/spotify/mobius.swift/wiki/Concepts The Update function defines state transitions. It takes the current model and an event, returning a Next object describing potential model changes and effects to trigger. Update functions must be pure. ```swift (Model, Event) -> Next ``` -------------------------------- ### Compose Effect Handlers with EffectRouter Source: https://context7.com/spotify/mobius.swift/llms.txt Use EffectRouter to declaratively route enum effect cases to handlers. Supports fire-and-forget, single-event return, async with EffectHandler protocol, and long-lived Connectables. Each effect case must have exactly one route. ```swift import MobiusCore enum AppEffect: Equatable { case playSound(name: String) case fetchUser(id: String) case navigate(to: String) } enum AppEvent { case userLoaded(User) case userFetchFailed(String) } struct UserFetcher: EffectHandler { func handle(_ userID: String, _ callback: EffectCallback) -> Disposable { let task = URLSession.shared.dataTask(with: URL(string: "https://api.example.com/users/\(userID)")!) { data, _, error in if let error = error { callback.end(with: .userFetchFailed(error.localizedDescription)) } else if let data = data, let user = try? JSONDecoder().decode(User.self, from: data) { callback.end(with: .userLoaded(user)) } } task.resume() return AnonymousDisposable { task.cancel() } } } let effectHandler = EffectRouter() // 1. Fire-and-forget .routeCase(AppEffect.playSound) .to { name in AudioServicesPlaySystemSound(1322) } // 2. Async with EffectHandler (cancellable) .routeCase(AppEffect.fetchUser) .to(UserFetcher()) // 3. Run on main queue, no feedback event .routeCase(AppEffect.navigate) .on(queue: .main) .to { route in // push a new view controller print("Navigating to \(route)") } .asConnectable ``` -------------------------------- ### Define Counter Events Source: https://github.com/spotify/mobius.swift/blob/master/README.md Define an enum to represent the possible events that can change the counter's state. ```swift enum CounterEvent { case increment case decrement } ``` -------------------------------- ### Route Effect to Specific DispatchQueue Source: https://github.com/spotify/mobius.swift/wiki/Effect-Handler Specify a DispatchQueue to run an effect handler on using the .on(queue:) modifier. This is useful for effects that must execute on a particular thread, like the main thread. ```swift EffectRouter() .routeCase(.myUIEffect) .on(queue: .main) .to { // Call some code that needs to run on the main thread } ``` -------------------------------- ### Update Function with Effects Source: https://github.com/spotify/mobius.swift/blob/master/README.md Augment the update function to return a Next object, which can include state changes and dispatched effects. ```swift func update(model: CounterModel, event: CounterEvent) -> Next { switch event { case .increment: return .next(model + 1) case .decrement: if model == 0 { return .dispatchEffects([.playSound]) } else { return .next(model - 1) } } } ``` -------------------------------- ### Group Similar Effects with Nested Enums Source: https://github.com/spotify/mobius.swift/wiki/Effect-Handler Group related effects by nesting enums. This promotes domain decomposition and allows for separate effect handlers for different categories of effects. ```swift enum Effect { song(Playback) navigateTo(NavigationTarget) } enum Playback { case play case pause case resume } enum NavigationTarget { case artist case home case search } ``` -------------------------------- ### Define Next for Update Function Return Source: https://context7.com/spotify/mobius.swift/llms.txt The `Next` type represents the outcome of an update function, allowing for model changes and/or effect dispatch. Use `.noChange` to suppress observer notifications. ```swift import MobiusCore struct LoginModel { var email: String var password: String var isLoading: Bool } enum LoginEvent { case emailChanged(String) case passwordChanged(String) case loginTapped case loginSucceeded case loginFailed(Error) } enum LoginEffect: Equatable { case attemptLogin(email: String, password: String) case showError(String) case navigateHome } func loginUpdate(model: LoginModel, event: LoginEvent) -> Next { switch event { case .emailChanged(let email): // Only model change, no effects return .next(LoginModel(email: email, password: model.password, isLoading: false)) case .passwordChanged(let password): return .next(LoginModel(email: model.email, password: password, isLoading: false)) case .loginTapped: // Model change + effect var loading = model loading.isLoading = true return .next(loading, effects: [.attemptLogin(email: model.email, password: model.password)]) case .loginSucceeded: // Only effect, no model change return .dispatchEffects([.navigateHome]) case .loginFailed(let error): var failed = model failed.isLoading = false return .next(failed, effects: [.showError(error.localizedDescription)]) } } ``` -------------------------------- ### Dispose of a Mobius Loop Source: https://github.com/spotify/mobius.swift/wiki/Mobius-Loop Shut down the Mobius loop and remove all associated observers. This is the normal way to stop the loop's operation. ```swift loop.dispose() ``` -------------------------------- ### Route Effects with EffectRouter Source: https://github.com/spotify/mobius.swift/wiki/Effect-Handler Use EffectRouter to define routes for each case of an Effect enum. Ensure the Effect enum conforms to Equatable for optimal performance. Associated values are automatically extracted and passed to handlers. ```swift let router = EffectRouter() .routeCase(Effect.a).to { ... } .routeCase(Effect.b).to { string in ... } .routeCase(Effect.c).to { (left, right) in ... } ``` -------------------------------- ### Update Function Signature Source: https://github.com/spotify/mobius.swift/wiki/Update The Update function signature defines how events are processed to produce a new model and optional effects. It can also be provided as a closure. ```swift Next.noChange() Next.next(model) Next.next(model, effects: effects) Next.dispatchEffects(...) ```