The need for architectural patterns in swift applications With the recent introduction of and , we will face some transition periods in our code base. Our applications will use both Combine and a third-party reactive framework, or both UIKit/AppKit and SwiftUI. This makes it potentially difficult to guarantee a consistent architecture over time. It is potentially hard to know when these new technologies will be incorporated in our projects. Making the right choice of architecture from the start may greatly ease the future transition. Combine SwiftUI Traditional architectural patterns like , , or mostly take care of the UI layer. They won’t be of great help when it comes to mix the aforementioned technologies inside your app in a unified way. For instance, MVVM in a UIKit application will rely a lot on two-way binding technologies with reactive extensions like RxCocoa or ReactiveCocoa. This will become less true as you gradually introduce SwiftUI and Combine. Chances are great that you will then have several architectural paradigms in your app. MVC MVP MVVM is a little bit more complete as it describes the routing mechanism between scenes and the separation between models (entities) and the business rules that manage them (interactor). This pattern enforces the principles of regarding separation of concerns and dependencies management. But, as well as the MVxx patterns, it does not guarantee a consistency of syntax and paradigm over time when adopting Combine or SwiftUI gradually. VIPER Clean Architecture SwiftUI is about state as the single source of truth and reacting to that state mutations. This allows for views to be written in a declarative manner, not in a verbose and error prone imperative manner. In recent years, several architectural patterns centred around the concept of State have emerged: things like or MVI and more generally unidirectional data flow architectures. Sometimes they offer to manage the state in a central fashion, sometimes in a local way. Those are great patterns and they fit very well with the idea of a state as the single source of truth. I’m pretty sure they had a great influence in the making of SwiftUI. Redux I myself have implemented some of these patterns in production applications. They introduced me to functional programming as they rely on concepts such as immutability, pure functions, function composition, and so on. Functional programming and state management fit very well together. Functional programming is associated to data immutability, so should be the state. Nevertheless, I experienced some drawbacks with these kind of architectures which led me to discover feedback loop systems. What is a feedback loop system ? A Feedback Loop is a system that is able to self-regulate by using the resulting value from its computations as the next input to itself, constantly adjusting this value according to given rules (Feedback Loops are used in domains like electronics to automatically adjust the level of a signal for instance). Stated this way might sound obscur and unrelated to software engineering, “adjusting a value according to certain rules” is exactly what a program, and by extension an application, is made for! An application is the sum of all kinds of states that we want to regulate so as to provide a consistent behaviour following precise rules. BUT The rules describing the allowed transitions from one value to another are described by a state machine. What is a State Machine? It’s an abstract machine that can be in exactly one of a finite number of states at any given time. The state machine can change from one state to another in response to some external inputs. The change from one state to another is called a transition. A state machine is defined by a list of its states, its initial state, and the conditions for each transition. As an application is a state machine, it can be driven by a feedback loop system. A Feedback Loop is based on three components: an initial state, a feedback, and a reducer. To illustrate each one of them, we will rely on a basic example: a system that counts from 0 to 10. : this is the starting value of our counter, 0. The initial state : this is the rule we apply to the counter to accomplish our purpose. The output of a feedback is a request to mutate the counter. If 0 <= counter < 10 then we ask to increase it else we ask to stop it. A feedback : this is the state machine of our system. It describes all the possible transitions of our counter given its previous value and the request computed by the feedback. For instance: if the previous value was 0 and the request is to increase it, then the new value is 1; if the previous was 1 and the request is to increase it, then the new value is 2; and so on and so on. When the request from the feedback is to stop, then the previous value is returned as the new value. A reducer Feedbacks are the where you can perform side effects (networking, local I/O, UI rendering, whatever you do that accesses or mutates a state outside the local scope of the loop). Conversely, a reducer is a pure function that can only produce a new value given a previous one and a transition request. Performing side effects in reducers is forbidden as it would compromise its reproducibility. only place The drawbacks of traditional unidirectional data flow architectures As you can see, the particularity of the feedback loop paradigm is that the state is both an input and an output, both connected as a whole to form a unidirectional loop. We can use the state as a local cache to persist data as long as the loop is alive and running. A typical use case would be the browsing of a paginated API. This kind of system allows for the use of the current state to always make the previous page URL and the next page URL accessible which makes irrelevant the need to store it elsewhere. In more traditional unidirectional data flow architectures, the state is only the output of the system. The inputs are “user intents” that trigger side effects and then state mutations. I’ve experienced several versions of these kinds of architectures (Redux and MVI) and found myself stuck with 2 majors issues: Not having the state as an input can lead to maintaining a local state in the UI layer, or in a cache repository. Relying on input such as Intents or Actions, which are often enums, forces us to parse them with `switch` statements to determine the side effect to execute. As we add new intents or new actions we have to change the way we parse them, going against the Open/Close principle of the SOLID rules. I’m not saying these issues are a “no-go” to use these architectures, nor am I saying I’ve used them the best possible way. For instance, I’ve worked on a variation of MVI where intents were replaced by a “command pattern”. Each command was responsible for its own side effect execution. There was no parsing, the command was self-sufficient. This approach is compliant with the Open/Close principle as adding a new feature is about adding a new command to execute; and not modifying the way intents or actions are parsed. But rather than twisting those architectures to accommodate my needs, I vote for an approach that naturally addresses these issues: feedback loop systems. What is Spin ? Let’s get back to our main concern: provide an architectural pattern that can absorb the divergence of technologies we can expect in our applications. As we saw, feedback loop is a pretty versatile pattern. It will help us mitigate the issue with mixed technologies. But we need a way to declare feedback loops in a unified way regardless of the underlying reactive framework or the chosen UI technology. This is where Spin comes into play. Spin is a tool to build feedback loops within a Swift based application allowing you to use a unified syntax whatever the underlying reactive programming framework and whatever Apple UI technology you use (RxSwift, ReactiveSwift, Combine and UIKit, AppKit, SwiftUI). Let’s try by building a system that regulates two integers to make them converge to their average value (like some kind of system that would adjust a left and a right audio channel on stereo speakers to make them converge to the same level). Spin We will need a data type for our state: { : : } struct Levels let left Int let right Int We will also need a data type to describe the transitions to perform on Levels: { increaseLeft decreaseLeft increaseRight decreaseRight } enum Event case case case case In order to describe the state machine ruling the transitions, we need a reducer function: -> { currentLevels. != currentLevels. { currentLevels } event { .decreaseLeft: ( : currentLevels. - , : currentLevels. ) .increaseLeft: ( : currentLevels. + , : currentLevels. ) .decreaseRight: ( : currentLevels. , : currentLevels. - ) .increaseRight: ( : currentLevels. , : currentLevels. + ) } } func levelsReducer (currentLevels: Levels, event: Event) Levels guard left right else return switch case return Levels left left 1 right right case return Levels left left 1 right right case return Levels left left right right 1 case return Levels left left right right 1 So far the code is not related to a specific reactive framework, which is great. Let’s write the two feedbacks that will have an effect on each level. With : RxSwift -> < > { inputLevels. != inputLevels. { .empty() } inputLevels. < inputLevels. { .just(.increaseLeft) } { .just(.decreaseLeft) } } -> < > { inputLevels. != inputLevels. { .empty() } inputLevels. < inputLevels. { .just(.increaseRight) } { .just(.decreaseRight) } } func leftEffect (inputLevels: Levels) Observable Event // this is the stop condition to our Spin guard left right else return // this is the regulation for the left level if left right return else return func rightEffect (inputLevels: Levels) Observable Event // this is the stop condition to our Spin guard left right else return // this is the regulation for the right level if right left return else return With : ReactiveSwift -> < , > { inputLevels. != inputLevels. { .empty } inputLevels. < inputLevels. { (value: .increaseLeft) } { (value: .decreaseLeft) } } -> < , > { inputLevels. != inputLevels. { .empty } inputLevels. < inputLevels. { (value: .increaseRight) } { (value: .decreaseRight) } } func leftEffect (inputLevels: Levels) SignalProducer Event Never // this is the stop condition to our Spin guard left right else return // this is the regulation for the left level if left right return SignalProducer else return SignalProducer func rightEffect (inputLevels: Levels) SignalProducer Event Never // this is the stop condition to our Spin guard left right else return // this is the regulation for the right level if right left return SignalProducer else return SignalProducer With : Combine -> < , > { inputLevels. != inputLevels. { ().eraseToAnyPublisher() } inputLevels. < inputLevels. { (.increaseLeft).eraseToAnyPublisher() } { (.decreaseLeft).eraseToAnyPublisher() } } -> < , > { inputLevels. != inputLevels. { ().eraseToAnyPublisher() } inputLevels. < inputLevels. { (.increaseRight).eraseToAnyPublisher() } { (.decreaseRight).eraseToAnyPublisher() } } func leftEffect (inputLevels: Levels) AnyPublisher Event Never // this is the stop condition to our Spin guard left right else return Empty // this is the regulation for the left level if left right return Just else return Just func rightEffect (inputLevels: Levels) AnyPublisher Event Never // this is the stop condition to our Spin guard left right else return Empty // this is the regulation for the right level if right left return Just else return Just No matter the reactive technology you choose, writing a feedback loop (also called ) is as simple as that: Spin levelsSpin = .initialState( ( : , : )) .feedback( (effect: leftEffect)) .feedback( (effect: rightEffect)) .reducer( (levelsReducer)) let Spinner Levels left 10 right 20 Feedback Feedback Reducer That’s it. You can use RxSwift in a portion of your app and Combine in another one, and all the feedback loops will use the same syntax. For “DSL-like” syntax lovers, there is a more declarative way: levelsSpin = (initialState: ( : , : ), reducer: (levelsReducer)) { (effect: leftEffect) (effect: rightEffect) } let Spin Levels left 10 right 20 Reducer Feedback Feedback How to start the loop ? .start(spin: levelsSpin) .disposed(by: disposeBag) .start(spin: levelsSpin) .disposed(by: disposeBag) .start(spin: levelsSpin) .store( : &cancellables) // With RxSwift Observable // With ReactiveSwift SignalProducer // With Combine AnyPublisher in Mixing reactive frameworks is no more an issue 👍. Using Spin in a UI perspective Although a feedback loop can exist by itself without any visualization, it makes more sense in our developer world to use it as a way to produce a State that will be rendered on a screen and a way to handle events emitted by the users. Fortunately, taking a State as an input for rendering and returning a stream of events from the user interactions looks A LOT like the definition of a feedback, and we know how to handle feedbacks 😁, with a Spin of course. Once a /feedback loop is built, we can “decorate” it with a new feedback dedicated to the UI rendering/interactions. A special type of Spin exists to perform that decoration: UISpin. Spin As a global picture, we can illustrate a feedback loop in the context of a UI with this diagram: In a ViewController, let’s say you have a rendering function like: { state { .increasing( value): .counterLabel.text = .counterLabel.textColor = .green .decreasing( value): .counterLabel.text = .counterLabel.textColor = .red } } func render (state: State) switch case let self "\(value)" self case let self "\(value)" self We need to decorate the “business” Spin with a UISpin (in the `viewDidLoad` function for instance). .uiSpin = (spin: counterSpin) .uiSpin.render(on: , using: { $ .render(state:) }) .uiSpin.start() // previously defined or injected: counterSpin is the Spin that handles our counter rules self UISpin // self.uiSpin is now able to handle UI side effects // we now want to attach the UI Spin to the rendering function of the ViewController: self self 0 // And once the view is ready (in “viewDidLoad” function for instance) let’s start the loop: self // the underlying reactive stream will be disposed once the uiSpin will be deinit Sending events in the loop is very straightforward; simply use the emit function: .uiSpin.emit( .startCounter) self Event What about SwiftUI ? Because SwiftUI relies on the idea of a binding between a State and a View and takes care of the rendering, the way to connect a SwiftUI Spin is slightly different, and even simpler. In your view you have to annotate the SwiftUI Spin variable with “@ObservedObject”: @ uiSpin: < , > = { spin = (spin: counterSpin) spin.start() spin }() ObservedObject private var SwiftUISpin State Event // previously defined or injected: counterSpin is the Spin that handles our counter business let SwiftUISpin return You can then use the “uiSpin.state” property inside the view to display data and uiSpin.emit() to send events. As a SwiftUISpin is also an `ObservableObject`, every state mutation will trigger a view rendering. (action: { .uiSpin.emit( .startCounter) }) { ( ) } (isOn: .uiSpin.binding( : \.isPaused, event: .toggle) { ( ) } Button self Event Text "\(self.uiSpin.state.isCounterPaused ? " Start ": " Stop ")" // A SwiftUISpin can also be used to produce SwiftUI bindings: Toggle self for Text "toggle" // \.isPaused is a keypath which designates a sub state of the state, // and .toggle is the event to emit when the Toggle is changed. The UIKit (AppKit) and SwiftUI ways of using a UISpin are very similar allowing you to integrate feedback loops previously written for UIKit screens inside new SwiftUI components. Mixing UI paradigms is no more an issue 👍. Conclusion We have reached our goal: to propose an architectural pattern implementation that can ease the transitions between incoming technologies. In the Spinners organization, you can find 2 demo applications demonstrating the usage of Spin with RxSwift, ReactiveSwift, and Combine. A basic counter application: and version UIKit version SwiftUI A more advanced “network based” application using dependency injection and a coordinator pattern (UIKit): and UIKit version SwiftUI version . PR are of course welcome (so as ⭐️ 😏). There is the Spin.Swift repo I am planning to develop a Kotlin implementation, compatible with both RxJava and Flow (any help would be appreciated). I hope you enjoyed this article. Feel free to leave comments so we can exchange about it.