Everyday Reactive

In this talk, we’ll walk through some practical uses of reactive programming in app development, using examples from my daily experiences. We’ll explore tips and tricks for determining when reactive programming can be a potent tool, as well as scenarios to avoid that might threaten code quality and performance. The talk will focus on concepts in reactive programming, the code will show off different Swift reactive implementations.


My name is Agnes. I’m here to share with you some tips, tricks, and things to avoid that I faced during my time learning reactive programming.

I’m an iOS developer at Ustream, which means I’m working on apps people use to watch and broadcast videos. It also means that we are shipping apps to millions of users, so we do our best to maintain a stable, readable, loosely-coupled code base to keep its quality high. That is one of the reasons why we write reactive code (and the other is because it’s fun). It doesn’t mean that we consider reactive programming as holy grail. We are mixing several paradigms, on purpose, and we are using different patterns that help us keep the desired level of quality in our code.

Reactive Programming

My favorite explanation about reactive programming is from Matt Gallagher: “Reactive programming manages “asynchronous data flows between sources of data and components that need to react to that data.” There is also a framework that he has written called CwlSignal, and it’s awesome. Let’s see what you gain by solving the problem of the asynchronous data that you need to react to it somehow, using the reactive paradigm.

The most important advantage is that it helps to reduce state. Instead of maintaining sometimes even complex state in our applications (which is definitely error-prone and difficult to test), we try to define the reactions to changes by creating different scenarios (like what you would do as a condition in the imperative world). They are branches of the certain data stream. The transformation and combination of these streams are made easy and powerful by certain operators like flatMap, Reduce, Combine Filter, and Sum. You can easily turn your code declarative this way, focusing on the what, not the how.

Is There A Framework For That?

What technology can we use to write reactive code in iOS? There are plenty: Rx Swift, Reactive Cocoa, Reactive Swift, Bond, and many more. Or you can write yourself. Andre Staltz wrote a reactive framework at a conference as a talk (so, should be easy, right?). These are all amazing projects, and quite a lot of them have great communities behind. Already, there are frameworks several years old, to frameworks that were written yesterday. The differences between these frameworks are mostly about their design and the level of control you have. There are microframeworks to use the most convenient features of the reactive paradigm.

Get more development news like this

Let’s say, observable streams of values with these transformations and combination operators, it’s already powerful. That’s what we do at Ustream with our own implementation. On the other end of the scale, there are the large frameworks. With those, you have all the power to build up every piece of logic or complex flow of your application reactive.

In this presentation, I’m going to break the code examples from several different frameworks on purpose, so you can see they all have the same idea behind. The point is that the concepts are similar, so you only have to learn them once, and the others should seem at least familiar.

When Do I Apply It?

I’ve heard a good argument about when to use reactive programming on our team. They say, under a certain level of complexity in the code, reactive makes it even more complex, but after a point, it makes complex code simpler.

Let’s say we have picked our use case, which is probably a complex application, and picked our framework. It generally works out well if we use reactive for bindings between views and viewmodels, or when reacting to certain async operations, like network requests. Or there is MVVM, because that’s what we are doing, but I could also say that the bindings between the view and the views logic. It works well because updating state based on the asynchronous events. On the UI or network operations is a classic example of complexity that reactive programming is meaning to solve. Lots of state changes of async events.

Observation

The simplest and most commonly used pattern in the reactive world is observing the values that travel on data streams we are interested in.

Let’s see how we can observe a certain event stream.


let (signal, observer) = Signal<String, NoError>.pipe()

signal.observeValues { animal in print( "value: \(animal)") }

observer.sendNext(value: "😸")
observer.sendNext(value: "🐰")

Here you can see the word signal, which is the core concept of ReactiveCocoa. It represents event streams that are observable and can populate events that either next values or errors or completed events or interrupted events on that stream. When there are new values on the signal, the values travels through the signal and then end up in the observing functions body.

Let’s Reduce Some state

Let’s see how to reduce some state with our reactive approach.


var loading: Bool
var userLoggedIn: Bool
var didShowAlert: Bool

Here have three flags, for example class, which we intend to set based on things like callbacks of API requests, or certain UI actions. When we have three flags, that means they can build up eight different state in their contact - eight different combinations you can have and you have to maintain. When we go reactive, you can see a dummy example of how to just derive the first value from the other two (see below). For three flags, it only means just four possible state. It’s already in.


var loading =
reactive(userLoggedIn) &&
reactive(didShowAlert)

Transform / Combine

Transforming or combining the streams can help reduce complexity and simplify code.


let loading = Observable.combineLatest(userLoggedIn, didLoadContent) {
    !($0 && $1)

}

loading.subscribe(onNext: { print("loading: \($0)") })

userLoggedIn.onNext(false)
didLoadContent.onNext(false)
userLoggedIn.onNext(true)
didLoadContent.onNext(true)

> loading: true
> loading: true
> loading: false

Here,it’s possible to merge two streams of data, and from that point, you’re operating with the elements from the combined stream. We combine two observables, and once both of them have a value, to combine latest, is being triggered with the latest value from both. This is a certain strategy of combining streams, but the strategy that you need when combining can vary, so there are, for example, merge, where you get all the values when they are coming in, or there is switch to latest, or zip. In this case, it’s in Rx Fifth.

Simplify / Unify Async Operations

You can turn other async operations (e.g. natural requests), into reactive streams. They can be handled exactly as, for example, click events of a button: you can combine them, and you can chain these operations as well. It means you no longer need specific metas for reacting to these (like passing over, completion handlers), or defining IP action methods, and maintaining a bunch of state for the proper behavior. They are practically identical streams, you can combine them with each other.


let catButton = UIButton(title: "😸")
let url = URL(string: "http://catfacts-api.appspot.com/api/facts?
number=1")

let catFact = URLSession.shared.rx.json(url:url!)
  .map( { return "\(parsedCatFact($0))" })

Observable.combineLatest(catButton.rx.tap, catFact) { c, fact in
    "\(c) 👉 \(fact)"
  }.subscribe(onNext: { print("\($0)") })


> 😸: Cats can be right-pawed or left-pawed.


Here, we are combining a button click with a response of a request - you get a catch fact when you click the button, which is super handy.

Trade-offs, Complications

1

The number one problem is debugging, because your call-stack is not your best friend anymore. At least it won’t be able to tell you what happened as precise as it would with a traditional imperative call-stack (which is, by the way, beautiful).


func logAnimals() {
    let animal = MutableProperty<String>("😸")
    let animalStream = animal.producer.logEvents(identifier: "📋")

    animalStream.start()
    animal.value = "🐰"
}

[📋] starting fileName: (...)/A.swift, functionName: logAnimals(), lineNumber: 16
[📋] value 🦊 fileName: (...)/A.swift, functionName: logAnimals(), lineNumber: 16
[📋] started fileName: (...)/A.swift, functionName: logAnimals(), lineNumber: 16
[📋] value


There are some tricks that can help. When you are suspicious of a certain stream posing some problem, there are ways to attach console logs to the event streams in some of the frameworks. Like here, it’s Reactive Swift, for example, here’s log events. But I think it’s called Debugging Rx Swift. It brings out where the values of the streams and where it happened. This hint could already save you time and headache, but it’s a difficulty you have to encounter.

2

There is a phase of learning reactive programming. When people realize the power of observation and want to use it everywhere, is fine, but for reducing KVO boilerplate code, a whole reactive framework might be a bit of an overhead, not just in size, but in performance as well.

Using observation to update state manually can easily lose the point of going reactive. Here, basically, it really just replaces KVO in this case. If you are already using reactive framework for that, you can just use bindings instead.

viewModel.title.bind(to: titleLabel)

Here we just bind a viewmodel’s title to a label and then we’ll update the label with text every time the viewmodel gets a new title. Embrace bindings. If you still need those conditions, to add some conditions to your logic, you can still add them, and just combine them into the reactive flow.

This is an example from Bond. You can have one-way bindings or two-way bindings, which comes handy when you like to define the connection between. For example, user input and the model representation of that data. It’s nice that you are able to inject side effects to a stream. Regardless of the fact that side effects are considered evil, they introduce complexity. The more side effects something has, the more difficulties to measure or define what’s going to happen after one single change.


viewModel.title.map { n -> Bool in
    return n != nil
}.bind(to: refreshButton.reactive.isEnabled)


viewModel.title.bind(to: titleLabel)
viewModel.title
  .bidirectionalBind(to: titleTextField.reactive.bnd_text)

3

There are different observables, like hot and cold. In the cold case, when injecting a side effect to a signal, it will perform every time there is a new subscriber to that signal. This is fine, because that is how it’s designed, but you have to make sure that you’re aware of how the implementation handles it. Make sure to only add side effects where it’s necessary. For example, when you want to add a basic tracking to your app, it makes sense to inject them in side effects to your event streams.


let catFact = URLSession.shared.rx.json(url:url!)
  .map( { return "\(parsedCatFact($0))" })
  .do(onNext: { _ in
      print("EFFECT")
  })

catFact.subscribe(onNext: { print("1. 😸: \($0)") })
catFact.subscribe(onNext: { print("2. 😸: \($0)") })

> EFFECT
> 1. 😸: Most cats adore sardines.
> EFFECT
> 2. 😸: In 1987 cats overtook dogs as the number one pet in America.


...
    .do(onNext: { fact in
        Analytics.trackEvent("cat fact generated: \(fact)")
    })

Also, make sure your observables are the kind you want to work with. For example, if it’s a button tap, it’s supposed to be a hot signal, which makes sense to send the analytics events when the button was tapped, not when someone subscribed for this update. This is the most difficult to prepare for, because once you get the feel of reactive programming, it can be challenging to say no to it… which is indeed necessary when it comes to keeping the bugs out.

4

I regularly found myself in situations in the past where I was trying to force reactive behavior onto some piece of code unnecessarily (that is, much time and not always useful!).

This would be a basic example of causing nice synchronization issues. Here we update the mutable items from different threads, which is easy to do also without any reactive, but it’s easier to fall into this trap this way. When subscribing, you have no idea where this event is coming from; at least, not from the surroundings of the code. This behavior might lead to unexpected results. It’s easily fixable, though, by generally designing your state to be immutable. For example, by returning new sets of items in this case, instead of modifying it, and by using schedulers to be in control of where the work is getting done.

Also, when there is a complicated network of logic behind sometimes just one little switch. This is cool, but I think it’s better to try keeping it simple, otherwise the effect of one change is going to be more than a person can just keep in mind all the time. Not to mention newcomers to the code who can’t even see where the events are coming from without understanding more layers of the application. The promise of reactive programming does not imply you have to give up solving something the traditional or imperative way when it makes more sense, or just simpler.

Designing APPIs

It’s important to keep in mind, especially because we all depend on a massive set of code (like the Apple framework), that’s mostly designed for traditional usage, which sometimes can be quite complicated to bridge to the reactive world. When you’re composing your APIs yourself, it’s nice to keep the reactive fields of it out of it, so you don’t force it on others. You might want to consider, though, to make sure your API is supported. You can do it by making sure your properties on your interfaces are observable, for example.

Resources

Bond RxSwift ReactiveCocoa CwlSignal cocoawithlove Justin Spahr-Summers - Enemy of the State Better code for a better world

Q & A

Q: How have you found onboarding new developers into a project when using one of the reactive frameworks? How have you found new developers coming on board on a project? Have you run into problems with that, or have you found it an easy process and people just pick up the new technology fairly well? Agnes: It has been difficult in the past, because when someone never seen reactive code before in their life, I think it’s quite not easy to learn. But they can catch up with it if they are working with the reactive code base every day. I think it’s slower than just getting to know a new code base, but it’s not that bad. I found recently that there is a trend in the community of trying out reactive and people giving talks on it. In the past year, all the candidates at our team, for example, whom I talk to with on the interviews, they were all aware of reactive programming. They all tried out at least on back projects. From that point, it’s not much of a difference.

Q: Out of all the libraries you’ve listed, which one does your company use the most? Have you tried them all, and which one did you use the most, and why? Agnes: I was working with a huge Reactive Cocoa objective C-code base, and all the others, I just tried out on back projects, and at Ustream, we are working with our own implementation, which is closed source. It’s lightweight. Just some signals and operators, like most of the microframeworks out there. It’s already helpful.

Next Up: RxSwift from Start to Finish #2: I create iOS apps - is RxSwift for me?

General link arrow white

Agnes Vasarhelyi

Agnes Vasarhelyi is an iOS developer at Ustream. She likes to build up software from streams of values and automate things in the meantime. Her blog tells you about reactive programming and her tweets about organizing community events.

Transcribed by Sandra Sanchez-Roige