Declarative Programming Using Swift 💋 RxSwift
One of the questions I get asked often is should I introduce RxSwift
in a legacy codebase? Before answering this question I would like to define the meaning of being declarative and why you should be declarative when coding.
👉🏿 Definition:
Declarative programming is a style of programming that actually expresses the computational logic without describing it’s workflow. It is self described with intent to communicate a clear correspondence and often said to be anything other than imperative programming.
🙄 Legacy:
This refers to a code that is no longer fully supported or which life cycle is ended. But in recent times engineers have come to describe legacy code as a poorly written code, lacks units tests, difficult to articulate and easy to break.
Now you might be wondering if all the code written before the gang of four published their book on design patterns are considered legacy code?. Well I will leave that to you to answer.
Another question is whether software developers write legacy code today? The answer to this is YES.
When you write a code that is incomprehensible, tightly coupled code or any code who’s intent is difficult to articulate.
💂🏿♀️Imperative vs Functional:
I don’t want to go into a holy war here because there are some veteran engineers who thinks that Functional
is crazy and just a super-flow thing which is not necessary. Well I want to tell you veteran that you are wrong.
The main problem of Imperative
coding paradigm is that it mutates the program’s State
with statements and uses subroutines to show intent for each given State
This naturally leads having a code base full of if, elseif else, switch, while & for
statements with tons of mutable data.
Functional
programming totally avoids State
mutation, it treats computation as an expression of intents and chains it all together making it declarative and easy to understand.
👉🏿 Becoming Declarative with RxSwift 💪🏾
RxSwift
is a member framework of ReactiveX
designed for the Swift
community. It embodies the concept of high level abstraction, reactive, functional and declarative programming which is required for asynchronous event driven computation and many more.
- 📝 Specification:
Let’s say you have a legacy app that downloads images from a resource from the cloud and you’re also required to show a loading indicator on the screen while download is going on in a background queue.
Imperatively when you think of the above specification you can understand that you need to start managing DispatchQueues
which can sometimes be a nightmare, depending on how complicated the requirements are.
My first advice is to introduce a RxSwift
through Cocoapods
into your code base as indicated in the configuration file below.
If you’re a fan of Carthage
you can do so as follows:
- 👀 ObserverType
Let’s assume you’re using MBProgressHUD
framework to manage your loading indicator. You could start by abstracting some of it’s imperative functionalities into an RxSwift ObserverType
as follows:
With 23
lines of code we’ve totally changed the way we use MBProgressHUD
and also added a flexibility that enables us to pass an Optional UIView
within a reactive context. UIView
when accessed from a UIViewController
is always Optional
so we can access it and use it directly without UnWrapping
and our ObserverType
will take care of the rest for us.
📒 This technique can be applied to SVProgressHUD
or any other loading indicators.
- 👁 Observable
Our model for the download should be pretty straightforward and this could come in form of an extension
on top of our legacy class
or a struct
if we are already using Swift
within our code-base. But as for best practices we should begin with a protocol as shown below:
The datasource
is an Observable
concrete type. If you are new to Reactive programming
the best way to understand is to visualize the process of how a publishing house gets the news across to it’s subscribers when they want to start listening or reading the news.
Observables
are very declarative and it helps you define a function
for publishing values, but it is not executed until a consumer subscribes to it.
- 🤗 Adoption
The Model
is self explanatory, we use the .rx
property provided by RxSwift
to access Observable
functions and we can add multiple transformative messages as functions on the same data. e.g .share
is saying the sequence shares a single subscription to all listening/observing.
- 𝌭 Declarative ViewModel
RxSwift
puts at your disposition computational tools that helps you become very declarative with your intent. The above protocol ViewModelType
enacts readability with the intent to drive an image and finish an event when something completes.
- 🤝 Conforming to the contract
As we can see from the above implementation, the code reads naturally like a romance novel with zero mystery and clear composition of functions on top of each other.
- 🐿 Subscribing to the news channel
From line# 22-25
we can clearly see how the image is driven from the source into the UIImageView.image
property through the imageView.rx.image
binding. This process is known as Subscription
to an Observable
and it’s exactly at this point that every other function along the Observable
chain is executed.
On line# 18
we did reactively used yet another trait property of RxSwift
called PublishRelay
to turn on the loading indicator, which we later turned off at line# 33
when the download is completed, and this is represented by our viewModel.finished
a Completable
Type.
- 🍱 Summary
Being declarative is not difficult as claimed by imperative old school. Declarative code helps us modularize our code, think abstractly by removing the hurdles of implementation details.
Observables are very good in expressing and communicating the developers intent. They are unidirectional data flow which make the application state read only. Changes are communicated purely as functions within the object tree, making it a single source of truth.
- 🤾🏾♂️ Playground
You can find the complete example to my rant here on the RxSwiftObserverType.playground and when you do a test drive from the playground you should see the image downloaded as shown below :-)