Functional Reactive Programming with RxSwift

Why does writing async code have to be a nightmare? Functional reactive programming aims to neat-ify your async woes by giving you the power to operate on closures the same way you operate on variables. RxSwift is a brand new library that aims to make your event-driven apps incredibly manageable and readable, all while reducing bugs and headaches. Max Alexander shows you the basics, and how functional reactive programming can do all this and more.


Introduction (0:00)

We have a lot of people who are talking about Rx these days. Rx is a generic abstraction of computation expressed through Observable<Element> interface, and RxSwift is a Swift version of Rx. It can get very overwhelming, but I’m going to try to explain it in a very introductory manner.

Who is guilty of writing code like this?

Alamofire.request(.POST, "login", parameters: ["username": "max", "password": "insanity"])
  .responseJSON(completionHandler: { (firedResponse) -> Void in
    Alamofire.request(.GET, "myUserInfo" + firedResponse.result.value)
      .responseJSON(completionHandler: { myUserInfoResponse in
        Alamofire.request(.GET, "friendList" + myUserInfoResponse.result.value)
          .responseJSON(completionHandler: { friendListResponse in
            Alamofire.request(.GET, "blockedUsers" + friendListResponse.result.value)
              .responseJSON(completionHandler: {

              })
            })
          })
    Alamofire.request(.GET, "myUserAcccount" + firedResponse.result.value)
      .responseJSON(completionHandler: {
      })
    })

What’s wrong with this code? These are Alamofire requests. If you guys don’t use Alamofire, it’s like AFNetworking and does HTTP requests. If you ever see nested block-based code start shifting to the right, you know you have a problem because you can’t tell what’s happening. You’re also talking outside of a network in this code, and networks can fail. You have error handlers, but you don’t know where you should handle all the error exceptions. Rx is something that could help with this.

Back to the Basics (2:26)

Whenever you have different events, you have a collection of them. We have this integer array, list, whatever you want to call it. [1, 2, 3, 4, 5, 6]. In the Swiftiest way possible I would get evens using filter.

[1, 2, 3, 4, 5, 6].filter{ $0 % 2 == 0 }

What if I wanted to get an array back out of all the numbers after it’s multiplied by five?

[1, 2, 3, 4, 5, 6].map{ $0 * 5 }

What about the sum of it all?

[1, 2, 3, 4, 5, 6].reduce(0, +)

This is very expressive. We’re not doing for loops and we’re not holding and maintaining states. It’s almost Scala-ish or Haskel-lish. Rarely do we have an opportunity to have an app that only uses array’s. Everyone wants the Internet. Everyone wants to be able to download pictures, talk to networks, add friends, etc. You’re going to be using IO a lot. And IO means you’re going to be talking outside your memory with human interaction, other devices, cameras, disks, and all sorts of things. They’re async. They can fail and be very problematic.

The Rx Bill of Rights (4:09)

I came up with the Rx Bill of Rights. The Rx Bill of Rights states:

We the people have the right to manipulate async events just like iterable collections.

Observables (4:25)

In the Rx world, let’s try to think Observables instead of arrays. Observables are a type-safe event that fire and push different sorts of data values over time. RxSwift is currently at the Beta 3 stage and is easy to install. All you have to do is import RxSwift.

pod 'RxSwift', '~> 2.0.0-beta.3'
import RxSwift

It’s easy to create and Observable. The simplest form of doing that is a just, a function that comes built into RxSwift. You give it whatever sort of a variable that you want and it will return an Observable of the same type.

just(1)  //Observable<Int>

What if we wanted to take that array and pump out events from that array, one by one?

[1,2,3,4,5,6].toObservable()  //Observable<Int>

That’ll give you an Observable<Int>.

If you have some sort of an API that uploads to S3 or saves data to a local database or API, you’ll probably do something like this.

create { (observer: AnyObserver<AuthResponse>) -> Disposable in

  return AnonymousDisposable {

  }
}

This will give you a block when you call for create. This block will give you an Observable which means something’s going to be listening to this. You can ignore AnonymousDisposable for right now. The next two lines that you see is where you stuff your API code to give yourself a nice Observable.

Here I have something similar to Alamofire.

create { (observer: AnyObserver<AuthResponse>) -> Disposable in

  let request = MyAPI.get(url, ( (result, error) -> {
    if let err = error {
      observer.onError(err);
    }
    else if let authResponse = result {
      observer.onNext(authResponse);
      observer.onComplete();
    }
  })
  return AnonymousDisposable {
    request.cancel()
  }
}

I’m going to log in or do a GET request and I’ll get back a callback of a result and error. I can’t really change this API because I’m given it by another client SDK, but I would like to turn it into an Observable. I say observer.onError() if there’s an error. That means, whoever’s listening to me, something failed. When you get the real response, you say observable.onNext() Then, if you’re completed with it you say onComplete() Now we get to the AnonymousDisposable. This AnonymousDisposable is the action that’s called in case you want to get interrupted. Say you leave your view controller or the app needs to be done with the service and you don’t need to call this request any longer. It’s great for video uploads or something much larger. You can do request.cancel() which cleans up all the resources when you’re done with it. This gets called on either completion or error.

Listening to observables (8:11)

Now that we now know how to create an observable, let’s look at listening to one. We give the array and then there’s this extension function that you can call on a lot of different objects. It’s called toObservable(). Then, you have your listener function.

[1,2,3,4,5,6]
  .toObservable()
  .subscribeNext {
    print($0)
  }

It’s like an iterable. The subscribe listener event can give you all sorts of different information based on failed requests, the next event or onCompleted. It’s your option to listen to these if you’d like.

[1,2,3,4,5,6]
  .toObservable()
  .subscribe(onNext: { (intValue) -> Void in
    // Pumped out an int
  }, onError: { (error) -> Void in
    // ERROR!
  }, onCompleted: { () -> Void in
    // There are no more signals
  }) { () -> Void in
    // We disposed this subscription
  }

Combining Observables (9:14)

A good example of using Rx is with some sort of a socket service. Say we have a web socket service that’s supposed to listen to stock tickers and show a UI with a person’s current account balance. As the stock ticker shows you different events, you’d like to be able to change if a person can buy. We’d like to disable the button if the balance is too low and enable it when the stock is within the buyer’s price range.

func rx_canBuy() -> Observable<Bool> {
  let stockPulse : [Observable<StockPulse>]
  let accountBalance : Observable<Double>

  return combineLatest(stockPulse, accountBalance,
    resultSelector: { (pulse, bal) -> Bool in
    return pulse.price < bal
  })
}

combineLatest says that for every single event that happens, we’re going to combine two of the latest events. This reduction block will fire depending if the stock ticker’s pulse price is below the balance. That means you have the ability to buy that piece of stock. This allows you to combine two of the observables and come up with logic to determine if something can pass. This gives you an observable of a Bool.

rx_canBuy()
  .subscribeNext { (canBuy) -> Void in
    self.buyButton.enabled = canBuy
  }

We take the rx_canBuy method that I created which returns a boolean after you’ve subscribed to it. Then, you can say self.buyButton is equal to the canBuy value that we get.

Let’s do a merging example. Let’s say we have a user interface that has my favorite stock ticker apps. I’m going to listen to Apple, Google and Johnson and Johnson. All these stock tickers will have different socket end points. I’d like to know and update my user interface whenever the stock ticker updates.

let myFavoriteStocks : [Observable<StockPulse>]

myFavoriteStocks.merge()
  .subscribeNext { (stockPulse) -> Void in
    print("\(stockPulse.symbol)/
      updated to \(stockPulse.price)/")
  }

These are all of the same type, an Observable of StockPulse. I’d like to know when any of them fires. All I have to do is an array Observable. I have multiple different stock tickers that I’m listening to and I’d like to merge them into one stream and listen to them.

Interactive Diagrams of Rx Observables (18:03)

I’ve done Rx for a very long time. Unfortunately, I still forget a lot of the operators and still have to refer to the documentation very frequently. This website, rxmarbles.com shows you the theoretical components of all these different events.

Background Processes With Ease (19:03)

There are some really awesome things that you can do in RxSwift. Say you have a video upload and it’s a very large file that you’d like to do in a background process. The best way to do it is with an observeOn.

let operationQueue = NSOperationQueue()
  operationQueue.maxConcurrentOperationCount = 3
  operationQueue.qualityOfService = NSQualityOfService.UserInitiated
  let backgroundWorkScheduler
    = OperationQueueScheduler(operationQueue: operationQueue)

videoUpload
  .observeOn(backgroundWorkScheduler)
  .map({ json in
    return json["videoUrl"].stringValue
  })
  .observeOn(MainScheduler.sharedInstance)
  .subscribeNext{ url
    self.urlLabel.text = url
  }

The video upload is going to give me the signals of all the percentages as it’s being done. But, I’m not going to do it on my main thread because I could do it on a background work scheduler. Whenever the video is done uploading, I’ll get a JSON back and it will give me a URL which I would like to put into a UI label. Because I said observe on the background work scheduler, it’s not on the main thread. I need to tell the UI with an update, and it has to be delivered on the main thread. Let’s switch it back and do observeOn(MainScheduler.SharedInstance) That will update your UI. Unfortunately, unlike other frameworks like RxJava on Android, if you deliver something on a background thread in Swift, it won’t yell at you. In Android it’ll crash, but here it won’t.

This Is Just the Surface of RxSwift (20:31)

I’ve shown you some of the cool things that you can do with RxSwift in trying to make your code easier and nicer by trying to think of events as arrays. I know MVVM is really huge lately to try to tame your view controllers from being this monolithic thing into a much more organized set. RxSwift has a sister library in the same repository called RxCocoa, which can help a lot with that. Basically, it puts extension methods on all the Cocoa classes for your UI views like Rx-Text or name fields such as that. You can write less of a subscribed next and you combined Observables to values at different viewpoints.

Multi-Platform (22:49)

This is a very multi-platform world that we’re living in. The main attraction for me in Rx was the ability to ignore every single other client API for doing IO. If you do Android or if you do JavaScript, you have to learn all these different ways of manipulating IO events in async. Rx is a family of helper libraries that you can attach into the more popular languages: .NET, Java, JavaScript, being the three really hot ones along with Swift. You can use the same operators and the same mentality to start writing your code. All these languages look very similar. Here’s a log in function. This is Swift.

func rx_login(username: String, password: String) -> Observable<Any> {
  return create({ (observer) -> Disposable in
    let postBody = [
      "username": username,
      "password": password
    ]
    let request = Alamofire.request(.POST, "login", parameters: postBody)
      .responseJSON(completionHandler: { (firedResponse) -> Void in
        if let value = firedResponse.result.value {
          observer.onNext(value)
          observer.onCompleted()
        } else if let error = firedResponse.result.error {
          observer.onError(error)
        }
      })
    return AnonymousDisposable{
      request.cancel()
    }
  })
}

You have rx_login which gives you an observable of the value that you want. And here’s the Kotlin version.

fun rx_login(username: String, password: String): Observable<JSONObject> {
  return Observable.create ({ subscriber ->
    val body = JSONObject()
    body.put("username", username)
    body.put("password", password)
    val listener = Response.Listener<JSONObject>({ response ->
      subscriber.onNext(response);
      subscriber.onCompleted()
    })
    val errListener = Response.ErrorListener { err ->
      subscriber.onError(err)
    }
    val request = JsonObjectRequest(Request.Method.POST, "login", listener, errListener);
    this.requestQueue.add(request)
  });
}

Looks very similar. And here is the TypeScript version.

rx_login = (username: string, password: string) : Observable<any> => {
  return Observable.create(observer => {
    let body = {
      username: username,
      password: password
    };
    let request = $.ajax({
      method: 'POST',
      url: url,
      data: body,
      error: (err) => {
        observer.onError(err);
      },
      success: (data) => {
        observer.onNext(data);
        observer.onCompleted();
      }
    });
    return () => {
      request.abort()
    }
  });
}

Also, looking really similar. You can write your test cases mindlessly against all these sorts of events. You can’t necessarily write all your client side code or your UI code because they all have their own special niches in there, but your service based classes can easily be abstracted the way of using Rx. Pretty much the same principles everywhere.

Q&A (24:42)

Q: I was wondering if you had any opinions on RxSwift versus ReactiveCocoa?

Max: I’ve done three years of Objective-C. ReactiveCocoa was my go to. When it was time to switch over to Swift, I installed the early version of ReactiveCocoa and it was giving me heck. I found RxSwift through a Google search and it worked. Personally, I’ve used ReactiveCocoa in both. People will say there are major and minor differences, but I’ve never heard anyone come up to me and say RxSwift ruined my childhood dreams or ReactiveCocoa has killed my baby. No one’s ever come to me with that sort of a difference. It’s up to you. RxSwift is under the ReactiveX GitHub repository, so if you feel really comfortable going up and then going down and reading the same sort of code, it’s a direct port. If your company is all about iOS and iOS, I say go ReactiveCocoa and don’t look back. But if you have three clients to prepare for, if you have an Electron app alongside with a JS app, it’s nice to be able to mindlessly put on Spotify and put on three monitors and copy your service. You’d be done with in a night.

Q: Do you have any recommendations or ways to deal with autocomplete not quickly working?

Max: I type really fast. Faster than the autocomplete. Most of the time I see the autocomplete hiccup right at the dot. The faster you type past that chasm, you’re over it. But these are the realities of Xcode at the moment. I have these go to methods and I don’t seem to have a problem after the autocomplete. It’s usually flatMap, merge and combineLatest.

Q: You mentioned cross-platform. Would this run on Linux?

Max: I said cross-platform-ish because this is a library that’s more of an API. It’s more of an API where you put the helper library with Java or you put the helper library with TypeScript, and it runs alongside.

Q: I noticed that this framework is importing foundation. I’m curious, would there be a huge barrier for getting rid of that dependency or using some replacement for it?

Max: I don’t know. I can get back to you on that. I’ll ask.

Q: How is debugging with RxSwift?

Max: There’s a debug function with blocking. There’s a sister library in RxSwift that does blocking. These are not to be used in production. These are really great for, if you need to stab it in there. It creates blocking calls, so you’re out of this async threshold. If you have something, now you’re on the main thread and you can do stack trace.

Q: I’m wondering if you think it’s a good idea to try to introduce RxSwift in very select cases in different places or if that’s going to create complexity since it’s not all using RxSwift or Reactive?

Max: A lot of the guys who contribute to RxSwift say you should live and breath RxSwift from the day one your project happens. But, that’s not realistic. Introduce it selectively. I don’t think most people in this audience have the luxury of starting an app from from new. You guys are dealing with five years, six years of different codebases. Introduce it selectively and see how you like it.

Q: I’ve done a bit of ReactiveCocoa and one of the things that they try to do with ReactiveCocoa is turn all events into signals so that you can put them all together. And they use selectors in Objective-C. Do you know of a way that you can do that, say with delegate callback methods in RxSwift, in Swift?

Max: Yeah. If you take a look at this code in RxCocoa, they have reactifying your UITableViewDelegates and UICollectionViewDelegates. It creates a delicate proxy where you can now have a closure to start pumping in events and you can inherit from Observable to create your own sort of Observables, to then pump signals down to the delegate level. It’s well done if you take a look at the RxCocoa Library.


Max Alexander

Max Alexander

Max Alexander has been a contract iOS Developer for the last 2 years and recently has been the iOS Engineer for Hipmunk. He deeply loves reactive apps and always loves to evangelize teams to use RxJS, RxSwift, and RxJava whenever possible. He has a particular interest in Operational Transform and Offline-First application development On his leisure time Max contributes to Github Projects, develops games on Unreal Engine 4 and Unity.