Doing it asynchronously: RxSwift vs PromiseKit

Swift   promisekit   reactive   promises   rxswift  

It's hard to write an app these days without having to include multi-tasking asynchronous code. Whether it's accessing a server's API or the local photo library, multi-threaded asynchronous programming is part and parcel of app development. Apple provides a range of APIs to help with asynchronous code and whilst they're very good, you still have to write a fair amount of code to use them. But are there any alternatives?

Well yes there are and over the past couple of years I've used two of them. But before we take a look, lets do a bit of a recap.

Calling asynchronous functions

There are a variety of signatures that asynchronous functions can follow, but for the purposes of this posting, I'll be using one based on functions used in Node.js:

func name(args:..., completion:(T?, Error?) -> Void)  

Essentially a closure is passed as a final argument to the function. Internally the function queues it's functionality on another thread and returns immediately. Once the code queued on the other thread finishes, the closure is called with either a result value, or an error.

Using a single asynchronous functions is relatively simple, but often you need to call more than one at a time or mix asynchronous and synchronous code. The end result can be messy, complicated, and often descends into what's known as 'Callback hell'. Lets look at a simple example of some asynchronous functions and how they can be called using Apple's multi-tasking APIs:

// Setup a background queue
let q = DispatchQueue.global(qos: .background)

// The two functions we are going to call
func foo(completion:@escaping (String?, Error?) -> Void) {  
    q.async {
        completion("abc", nil)
    }
}

func bar(_ arg:String, completion:@escaping (String?, Error?) -> Void) {  
    q.async {
        completion("\(arg)def", nil)
    }
}

// Call the functions
foo { resultFoo, errorFoo in  
    guard let resultFoo = resultFoo else {
        print("Error \(errorFoo)")
        return
    }
    bar(resultFoo) { resultBar, errorBar in
        guard let resultBar = resultBar else {
            print("Error \(errorBar)")
            return
        }
        print("Success \(resultBar)")
    }
}

That's a lot of boilerplate, both to execute the asynchronous code and to call the functions. Error handling is prominent, as is manually having to create the background queue. Also notice how the second call must be nested within the closure of the first so it can use the result of the first function and not execute too soon.

Imagine what would happen if we needed a 3rd, 4th or 5th asynchronous function was needed. Ugg!

PromiseKit

After writing a fair amount of my own asynchronous code similar to this, I started looking for a better way to do it and came across Promises. Promises made a lot of sense and I was soon using Bluebird promises for Node.js and PromiseKit for Swift based iOS projects.

Promises work differently to the code we just looked at. They don't immediately queue their functionality for execution, instead you create a chain of promise objects containing the code you want to execute. Then at some later time the whole chain is triggered, each promise executing on another thread in turn then passing the result to the next promise in the chain.

Because promises are objects and writing them is about assembling a chain to execute, it opens up a variety of possibilities. Factory methods and extensions for example, become quite common place and give you flexibility both in how you build the chain and how you test it.

Like any new idea, it did take me a little time to get my head around the core concept and realise the possibilities. But once I did, promises massively reduced the amount of code I was writing and improved it’s readability and testability. So let’s see the horrible example above re-written using PromiseKit:

// The two functions we are going to call
func foo() -> Promise<String> {  
    return .value("abc")
}

func bar(_ arg:String) -> Promise<String> {  
    return .value("\(arg)def")
}

// Call the functions
foo()  
    .then(execute:bar)
    .done { result in
        print("Success \(result)")
    }
    .catch { error in 
        print("Error \(error)")
    }

Holy cow! That's a vast improvement. The error handling's gone, the nesting is gone and the asynchronous boilerplate is gone. PromiseKit is taking care of it all, plus the threading and even the passing of results. You could rightly be cynical about now, thinking this is way to easy, but my experience is that even in real world projects, PromiseKit keeps things this simple.

PromiseKit also offers functions for auto-wrapping and returning promises from asynchronous functions with signatures like those in the first example, managing parallel execution of promises is a variety of ways, mapping of values, cancellation and much more. Oh, and it's documentation is quite good so go have a read.

Apart from the core project, PromiseKit has a number of sibling projects which provide all sorts of extensions and wrappers for other 3rd party APIs. Alamofire, UIKit and AssetKit to name but a few.

To be honest, there are (or should I say were) things that I’ve regarded as niggling issues with PromiseKit, code completion issues and incorrect errors in Xcode being the most common. To their credit, the authors of PromiseKit have responded by recently releasing PromiseKit 6, a significant update that shows shows a willingness to take an open and honest look at their own work and to go in boots and all, refactoring and extending to fix issues and take the kit to the next level.

RxSwift

The other API I want to talk about is one I’ve encountered on a project at work. RxSwift is a Swift implementation of ReactiveX. It's the new kid on the block and part of the Reactive programming trend.

RxSwift code works differently to promises. The core idea is based around the concept of processing a stream of ‘events’ using Observables. Observables can be both a source of events and process the events from other observables. Like PromiseKit, using RxSwift is a matter of chaining object, in this case - observables.

Reactive implementations such as RxSwift are particularly good for situations where you are doing things like reading text from a file, getting blocks of information from a server, or handling multiple taps on a button. So lets look at our example, now coded in RxSwift:

// The two functions we are going to call
func foo() -> Observable<String> {  
    return Single<String>.create { single in
        single(.success("abc"))
        return Disposables.create()
    }
}

func bar(_ arg:String) -> Observable<String> {  
    return Single<String>.create { single in
        single(.success("\(arg)def"))
        return Disposables.create()
    }
}

// Call the functions
let disposeBag = DisposeBag()

foo().concat(bar())  
    .subscribe(
    onSuccess { result on
        print("Success \(result)")
    }
    , onError { error in 
        print("Error \(error)")
    })
.disposed(by:disposeBag)

Like PromiseKit, RxSwift saves us from having to deal with multi-tasking and simplifies the code. The main difference is because of the way RxSwift (Reactive) works, it needs you to manage deallocating the observables you create. You do this by declaring an instance of DisposeBag and registering the observables with it. When that deallocs, the observables dealloc with it. If this isn't done then theoretically the observables will stay in memory, sending events forever.

Apart from the core functionality, RxSwift also has a range of pre-built functions which allow some quite complicated processes to be built with very little code. Then there's a range of more defined Observable known as traits. Traits help to clarify the type of results you can expect. For example, Single for asynchronous code that produces a single result and Completable if your asynchronous code that doesn't return anything.

RxSwift does has some issues though.

Personally I don't like having to declare and use DisposeBag instances. It can cause memory leaks if you get it wrong and it feels like RxSwift's internals are spilling into my code. It's... unsightly.

I also tend to think that whilst RxSwifts functions and traits give it great power, it can also create complexity, increasing the time it takes to write and decipher the resulting code. Obviously given time this won't be so much of an issue, but RxSwift's functions and traits aren't something that can be quickly picked up and used. They're not helped by a lack of clarity in some areas either. For example, the then(...) and concat(...) functions appear to do the same thing and have identical documentation. So do we need both?

RxSwift's documentation is also not great. The function reference points to a ReactiveX.io website, but it's written as a general Reactive resource and it's not unusual to find a function listed there that RxSwift doesn't implement or implement the same way.

Finally whilst RxSwift traits can help clarify things, they have different interfaces and sometimes aren't compatible with each other where you might like them to be.

So...

I think it's pretty clear I'm no fan of RxSwift. I'll grant it has some useful things to offer and if you're willing to spend time on it, it's function library provides a lot of nifty stuff. It's also worth considering when you are dealing with streams of events which is what Reactive is all about.

Reactive and therefore RxSwift are also trendy at the moment and as with anything that's 'in', be weary of those drinking the Kool-Aid and selling it as the answer to all your coding problems. Reality is - RxSwift is suitable for some things and not others. Understand where it fits and use it appropriately.

PromiseKit on the other hand doesn't have a large library of cool functions, but those that it does have are very carefully chosen and work well. Overall PromiseKit has a light touch on your code, is easy to remember and basically - just works. In other words, PromiseKit ticks all of my boxes.


Comments powered by Disqus