Swift Business logic component (BLoC)

Victor Kachalov
CodeX
Published in
4 min readJul 15, 2021

--

Alongside with native development for iOS or Android, there are plenty of different cross-platform solutions and of the them is Flutter. This brings one interesting approach of dividing business logic and UI called BLoC — Business logic component pattern.

Originally presented as Flutter packages flutter_bloc andbloc by Felix Angelov the BLoC brings a new declarative way how to use the Event/State mechanics of each component of the app.

The solution truly inspired me to create something similar for iOS native development, although we have already a set of good architecture patterns — MVC, MVVM, VIPER. Why we need another one?

Personally, I see the BLoC pattern as a simplified MVVM pattern with lowering concept of reactive binding. The idea of Input-Output i.e. Event-State is considered as something one and whole. So why not to give it a try…

With the help of Combine Framework, I was able to create the BLoC library in pure Swift, which can be a good addition for any SwiftUI built App.

You may check the source code here and of course Contributers are welcome :)

We will try to adjust the beloved Flutter’s starter app “Counter” but for native iOS with SwiftBloc library.

The App will be single view and implements only two operations — increment and decrement by one every time a user taps appropriates buttons.

States and Events

Everything what is happening in the app’s workflow can be divided to events and states. Events provoke states to be changed. And this leads to more clear imagination how the app should behave without describing little details to make things work.

We agreed that the Counter app can only add and substract “1”. Let’s create a simple enum for these.

enum CounterEvent {   case increment   case decrement}

Ok, pretty simple, as we agreed the user emits only two events in this app, so we don’t need anything more.

Now let’s think about the possible States. We know that the idea is, that if any of the two events increment or decrement are called, the counter will be increased or decreased by “1”. So we may just create a CounterState with a variable which will simply hold an integer with updated value.

struct CounterState: Equatable {   let count: Int}

Bloc requires unique behaviour for every state and event, that is why CounterState should conform Equatable.

Already at this stage we can think, that the initial state can be CounterState(count: 0) and next states could be CounterState(count: N) where N = currentState “plus” or “minus” 1.

Now let’s create a Bloc class to operate with events and states.

final class CounterBloc: Bloc<CounterEvent, CounterState> {   init() {      super.init(initialState: CounterState(count: 0))   }   override func mapEventToState(event: CounterEvent) -> CounterState {      switch event {      case .increment:         return CounterState(count: state.count + 1)      case .decrement:         return CounterState(count: state.count - 1)      }   }}

At first we need to inherit from the generic Bloc class and provide types for our events and states.

We need to provide an init() and call the super constructor as well to pass the initial State of CounterState.

Important thing here is to override the method mapEventToState — this is the main idea of how Bloc works: every event is just mapped to the state as you need. Without override, preconditionFailure will crash the app.

Pretty clear — if event is increment then the counter state will get a new value of CounterState providing as count parameter the actual current state with increasing by 1. Else — the same, but with decreasing by 1.

Now let’s adjust the Bloc logic in one SwiftUI View.

struct BlocContentView: View {   var body: some View {      BlocView(builder: { (bloc) in         VStack {            Button(action: {               bloc.add(event: .increment)            }, label: {               Text("Send Increment event")            })            Button(action: {               bloc.add(event: .decrement)            }, label: {               Text("Send Decrement event")            })            Text("Count: \(bloc.state.count)")         }      }, base: CounterBloc())   }}

Here we create a simple SwiftUI View BlocContentView.

The most interesting thing is the BlocView view inside the body property.

BlocView expects @ViewBuilder closure builder passing a closure parameter which references to the Bloc object which is provided for constructor parameter base. Good to know — this Bloc object is “drilled” through all the subviews because it is an EnvironmentObject.

Creating a simple VStack with a couple of buttons and a text we can we provide our desired behaviour for this View.

Let’s build it and play around.

Our initial state. The counter is at zero, just as planned.

If we tap two times on Send Increment event button

Not lets tap 5 times on Send Decrement event button

One thing to notice. With BlocView when any state changes, the whole View is not rebuilt i.e the body property is not called! Only the builder closure is getting called.

And of course you can still use SwftUI Binding wrapped above the BLoC state instead of “@State”:

CounterView()   .alert(isPresented: Binding.constant(bloc.state.count < -6)) {      Alert(         title: Text("Hi"),         message: Text("Message"),         dismissButton: .cancel {            for _ in 0..<6 {               bloc.add(event: .increment)            }         }      )    }

This approach allows you to optimise your app and reduce the redundant calls of body property to redraw the whole View.

--

--