Reactive Swift

My journey with reactive programming in Swift — and the iOS app that came out of it.

Agnes Vasarhelyi
Swift Programming

--

UPDATE: ReactiveCocoa‘s new Swift API is out now. You can find my new post about how I upgraded this iOS app with the latest ReactiveCocoa on All The Flow blog.

Let’s go back in time to when Apple introduced Swift, the successor to good old Objective-C, it was a very different time. I myself was extremely excited about having a new language on the table, especially one with a background in scripting languages, but still type-safe. Although Swift has grown rapidly ever since, we don’t yet consider it production ready. When that time comes, I want to be familiar with how to deliver clean, testable code…with a reactive UI (if I could have any wish)? Well, read on, if you’re interested in something besides the ordinary Objective-C & MVC world.

MVC vs. MVVM

Let’s start from zero. When designing an application you probably start by considering the architecture you want to build. Cocoa comes with Model-View-Controller (alias MVC), see my most favourite ever diagram below.

Although you might not find this architecture efficient for your design, we can see in the picture, the controller rules everything (the clue is in the name). If you have experience with MVC on iOS/Mac, I bet you’ve already seen view controllers responsible for a lot more than you wanted in the first place. The more you work with MVC, the sooner you’ll face the fact that you have to figure out a different way of solving your problems. When it comes to handling network communication methods — which is not exactly black magic nowadays — you’ll end up just like Thomas the Tank Engine, working a big fat controller, responsible for getting the data from all sources and rendering them to the UI at the same time. Even worse, have you ever tried to cover a view controller with unit tests? Painful stuff for sure, having a view involved in tests, testing the presentation logic. With the view’s complexity growing, it’s just not worth the effort. That’s why people usually don’t test view controllers inside MVC.

Model-View-ViewModel (MVVM) though, can be better for your app’s design for a lot of reasons. It comes from Microsoft and is a powerful architectural pattern for event-driven implementations. As you can see, here the view owns the view model, unlike the MVC case.

Naturally, it depends on what are you working on and how you’d like to scale it, but at the end, it means less complexity and better testable code. At the same time? How is that possible? It’s all about moving logic to the view model and reducing the responsibility of the view controller.

Allow me to use one of my pet projects as an example to introduce you to the concept. It’s the iOS client of a semi-automated brewing machine that helps reach and hold temperature levels during the brewing process (extremely important work). If you want to read more about the topic, check out this article about the home brewer. The point is, we would like to see the temperature — amongst other information — of the current brew all the time within the app.

Let’s get back to MVVM, and the temperature we were looking for. To be able to continuously read that value, we will use WebSocket with our server and an MVC app for showing it on an iPhone. At first, we need a Brew model with the desired temperature stored in it.

class Brew {
var temp = 0.0
}

We also need a Brew view controller, showing the temperature of the beer we are currently brewing.

class BrewViewController: UIViewController {
@IBOutlet weak var tempLabel: UILabel!
}

In the old MVC way, we would implement the network logic inside the view controller and update the UI accordingly.

socket.on("temperature_changed", callback: {(AnyObject data) -> Void in
self.brew.temp = data[0] as Float
dispatch_async(dispatch_get_main_queue(), {
self.updateTempLabel()
})
})

And that’s how we update our UI:

func updateTempLabel() {
self.brewLabel.text = NSString(format:"%.2f ˚C", self.brew.temp)
}

Well, we have everything in one place now, pretty awesome, isn’t it? No it’s not.

Imagine the case when you have to authenticate a user first, or draw live charts, or handle input fields, then what? Check out the complete MVC example. But now check how it looks in the MVVM style:

var brew = Brew()
dynamic var temp: Float = 0.0 {
didSet {
self.brew.temp = temp
}
}

These are the variables to store inside the view model, they look a bit strange, but I couldn’t see how to make it less weird (and I also wanted to shed a negative light on this solution, maybe I’m kind of biased already). Anyway, let me know how would you do it! Now back to the socket:

socket.on("temperature_changed", callback: {(AnyObject data) -> Void in
self.temp = data[0] as Float
})

So the network logic has moved to the view model. But how to notify the view about the changes in the model? Key-Value Observing (KVO) is our friend. Here’s how our viewDidLoad will look:

self.brewViewModel.addObserver(self, forKeyPath: "temp", options: .New, context: &brewContext)

So we need to observe the temperature somehow. For that, I used a separate field for the view model. You might be asking, why do we even care about having a Brew model inside the view model? I would say then, future Brew model could have different fields (hints on the screenshot above), and you might want to serialize it, send it over the network, or something. We need to have it’s state.

override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if context == &brewContext {
dispatch_async(dispatch_get_main_queue(), {
self.updateTempLabel((change[NSKeyValueChangeNewKey]) as Float)
})
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}

Wow, that’s.. a lot? There comes a huge amount of boilerplate code with KVO implementation, but it’s still more lightweight than an application written in the MVC style, especially when we think of putting more and more complexity into our application — into our view model. You can read the whole snippet of the MVVM example.

Anyway, it’s much better, but I still have the feeling we can improve somewhere. Now that we understand that the key for representing mutable models in MVVM’s view is data binding, what can be better at carrying out our bindings than some reactive magic?

Reactive programming

“Reactive programming is programming with asynchronous data streams.”

If you are not familiar with this quote, I suggest to read the article about reactive programming by @andrestaltz, it’s a pretty cool introduction. Basically, in the reactive world, everything is a stream, even a stream of streams is a stream. Check out this picture, representing button clicks / touch events as a stream:

And this important safety message about streams.

Look at our example this way: Consider the socket data as a stream of temperature values and the UILabel listening to it. Transforming them meanwhile is where the functional magic comes into the picture, operators like map, merge, filter and a lot more, amazing! Let’s see how all of these work together in Cocoa.

ReactiveCocoa

When we plan to do some reactive programming with Cocoa, the most popular choice is to use ReactiveCocoa (aka RAC) by GitHub. ReactiveCocoa is an FRP inspired powerful data binding framework.

It operates with signals, which are defined as push-driven streams. It means, that a signal is a representation of an asynchronous flow that will deliver data or any kind of result to it’s subscribers in the future. In addition to modelling changes with signals, RAC also provides built-in UIKit/AppKit bindings, like rac_textSignal and a lot of other helpful stuff.

All the code in this article uses RAC 2.4, but there is already a release candidate for the 3.0 of ReactiveCocoa. Check out my new post about how I turned this app using ReactiveCocoa 3, if you are interested in upgrading.

Except for some tiny details, like having not occupied and alike, you are able to write code with RAC 2.4.x in Swift. Also, Swift has built-in functional operators for its collection types, which comes handy all the time. No need for loops anymore!

Let’s see how can we build a ReactiveViewModel for our app, using ReactiveCocoa as the binding system for the MVVM architecture. Our view controller looks a lot better now, see?

self.brewViewModel.tempChangedSignal.map {
(temp: AnyObject!) -> AnyObject! in
return NSString(format:"%.2f ˚C", temp as Float)
} ~> RAC(self.tempLabel, "text")
}

The RAC macro used in Objective-C before has been replaced, since C style complex macros are not available in Swift. Thank Yusef Napora for the straightforward solution and @ColinEberhardt for wrapping it up in his sample app.

Now back to the brewing, it’s so reactive already! Let’s see the other end of the stream.

socket.on("temperature_changed", callback: 
{(AnyObject data) -> Void in
tempChangedSignal.sendNext(data[0]).deliverOn(RACScheduler.mainThreadScheduler())
})

Here, we are just creating signals of temperature data straight from the socket, and making sure the updating of the UILabel of the view controller will occur safely on the main thread. Ah, dammit, I can’t resist showing another example of how you can leverage the power of RAC. Now it comes with an HTTP request, handling the unfortunate error when trying to initiate a new brew on the server.

NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler: {
(response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
if error == nil {
subscriber.sendNext(JSON(data))
subscriber.sendCompleted()
} else {
subscriber.sendError(error)
}
})

Here we send the received JSON to the subscribers on success and an error signal on failure. Handy, isn’t it?

syncSignal.subscribeError({ (error: NSError!) -> Void in
UIAlertView(title: "Error creating brew", message: error.localizedDescription, delegate: nil, cancelButtonTitle: "OK").show()
})

We just had to subscribe for the error events in the view controller and wait for the magic to happen when we, for example, lost connection.

What was it all about?

It was actually a real life example I brought up. I started with the plain old MVC version of the app, first in Objective-C. It was pretty straightforward, no surprises. Then Swift occurred, and I of course I fall in love with any such innovation whenever I see it and so decided to rewrite the entire app. After struggling a lot with the heavy controllers and my UI refresh mechanism, which I was not exactly proud of, I decided to go reactive. I was already familiar with the fact that it goes well with MVVM, so I gave it a try. The experience was like erasing half of my code. Then, transforming the app with ReactiveCocoa felt like doubling the value of the remaining half. Since then, adding tweaks (like changing a button’s state depending on some data) is basically writing an enabled signal to the button’s command, which is like writing one line of code, almost no effort. Think about how long it would take with the good old way.

There are so many other things you might wanna try out with ReactiveCocoa and with Swift as well. If you are interested in brewing some beer with this combination, or just the code itself, check out the full source of BrewMobile, the iOS client of @brewfactory. Are you aware of a technology to make the app even better, or just more delightful? Let me know! And of course, feel free to contribute.

And to end with, some insights about the future of BrewMobile, I’m looking forward to introducing React Native to brewing as soon as possible. I will of course share my experiences if React Native turns out to be all I think it might be.

Extra stuff you might want to read:

Swift

http://www.objc.io/issue-16/power-of-swift.html
https://designcode.io/swift-design

http://blog.scottlogic.com/2014/07/24/mvvm-reactivecocoa-swift.html
http://www.objc.io/books/

MVVM

http://www.objc.io/issue-13/mvvm.html
http://www.teehanlax.com/blog/model-view-viewmodel-for-ios/

FRP

https://gist.github.com/staltz/868e7e9bc2a7b8c1f754
http://blog.risingstack.com/functional-reactive-programming-with-the-power-of-nodejs-streams/
http://rxmarbles.com

ReactiveCocoa

https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/DesignGuidelines.md
http://nshipster.com/reactivecocoa/

--

--