Custom convenience operators with RxSwift, Part 1

Intro

Just like when learning a new language you need to build up a dictionary to start understanding how the language works, you got to learn the Rx operators and everything will eventually click together.

And then once you have a good common of a language it’s okay to start coming up with new words too to boost the expressiveness of your speech and for convenience.

Same thing happened with me last week - for the first time I felt like I’m getting work done with RxSwift because I had good understanding how to use at least few operators. Naturally I wished to create my own custom convenience operators that would not do anything essentially new but will just help me express myself better and clearer.

replaceWith(value): Replace any element with a constant

When I just want to react to a certain group of events I replace the actual value emitted so that I can merge two or more Observables into one stream and observe that single stream.

The following piece of code is from last weeks post and observes the taps on the Start and Stop buttons of the laps timer app:

let isRunning = [btnPlay.rx_tap.map({_ in true}), btnStop.rx_tap.map({_ in false})]
	.toObservable()
    .merge()
    .startWith(false)
    .shareReplayLatestWhileConnected()

I was looking at this chunk of code and thought that there should be a cleaner, more readable way to represent the mapping parts. What I do there for both observables is to ignore the actual value and replace it with a constant.

So I dug through the RxSwift code and based on what I found there I put together this brilliant (hic) piece of code:

func replaceWith<R>(value: R) -> Observable<R> {
    return Observable.create { observer in
        let subscription = self.subscribe { e in
            switch e {
            case .Next(_):
                observer.on(.Next(value))
            case .Error(let error):
                observer.on(.Error(error))
            case .Completed:
                observer.on(.Completed)
            }
        }
        return subscription
    }
}

I create and return a new Observable and pass through the Error and Completed events, but replace the value of the Next event with the value constant. Seems good?

What looked like a magnificent piece of code turned out to be a bit of an overkill. I mean after all I just wanted to “map” any value of any type to a constant and when you say it that way the code almost writes itself! So in the end I rewrote the code like so:

extension ObservableType {
    func replaceWith<R>(value: R) -> Observable<R> {
        return map { _ in value }
    }
}

As you can see I didn’t have to go crazy about the whole thing but just literally take the piece of code I wanted to re-use and abstract it in a method on the ObservableType.

With this the same code block from the beginning of the post looks like:

let isRunning = [btnPlay.rx_tap.replaceWith(true), btnStop.rx_tap.replaceWith(false)]
	.toObservable()
    .merge()
    .startWith(false)
    .shareReplayLatestWhileConnected()

Awesome! Having my own custom convenience operator made the code less prone to errors (no custom code in a closure to write) and more readable.

At this point I started doubting myself a bit - this was too good to be true honestly. I thought I must be doing something wrong :)

However it turned out many people have this exact custom operator in their code base, it apparently solves a common problem.

Then I got a bit crazy and decided just for fun to explore how much further I can take this.

replaceWithDate(): Replace with timestamp of the latest value

Since I was already warmed up by putting together replaceWith I thought it’d be fun to have a convenience operator to give me the timestamp of the latest element from the observable sequence.

In this concrete case the constant I’d replace the element with would just be the current date:

extension ObservableType {
    func replaceWithDate<R>(value: R) -> Observable<NSDate> {
        return map { _ in NSDate() }
    }
}

Now I can bind the latest value from an Observable to a label, and show the timestamp of that value in another label like so:

let count = Observable<Int>
    .interval(3, scheduler: MainScheduler.instance)
    .shareReplay(1)

count.map {counter in "\(counter)"}
    .bindTo(label1.rx_text)
    .addDisposableTo(bag)

count.replaceWithDate()
    .map {$0.description}
    .bindTo(label2.rx_text)
    .addDisposableTo(bag)

And here’s the result (wait few seconds to see the increments):

negate(): Negate the value of the element

Next I noticed that sometimes I need to bind an Observable to rx_enabled property of a button, and sometimes to rx_hidden. While writing binding code I had to use numerous map {value in !value}, which made my code less readable.

If you check last week’s post you will see that in my effort to increase readability I ended up having two observables: one called isRunning and one isntRunning.

After another read through some of RxSwift’s code I learned how to add an operator to an Observable of a certain type. In my case I wanted to add the negate() operator to just observables producing Bool values.

Observable exposes its elements’ type as Element and I could easily match this to the BooleanType (Swift ftw!):

extension Observable where Element: BooleanType {
    public func negate() -> Observable<Bool> {
        return map {value in !value}
    }
}

Sweet - thanks protocol extensions with associated types! Now I could easily write code like:

active.bindTo(btnStart.rx_enabled).addDisposableTo(bag)
active.negate().bindTo(btnStart.rx_hidden).addDisposableTo(bag)

This code will both enable and show the button whenever active emits a true element. Pretty sleek eh?

To wrap up today’s post here’s what I’ve also added to my project a bit later:

extension Observable where Element : SignedIntegerType {
    public func negate() -> Observable<E> {
        return map {value in -value}
    }
}

Now negate() worked also in other contexts. If you used it on Observable<Bool> it would apply a logical not to the value; if you used it on a Observable<Int> it would produce the negative value of the element. Cool!

Conclusion

Creating your own convenience operators is awesome. The code is more readable, there is less opportunity to introduce bugs, and there’s nothing wrong with it.

In my next post I’ll look into few more operators I created along the way for myself. Do you want to share any of yours?

Do you know a better way to do any of this? Seen a bug? Ping me on Twitter.

Hope that post was helpful, and if you want to get in touch you can find me here

Share this post:


If you'd like to learn how to create professional production apps with RxSwift, the best resource out there is the RxSwift book written by Florent Pillet, Junior Bontognali, Marin Todorov, & Scott Gardner.

It features 20+ chapters covering the basics, the Rx operators, and advanced topics like testing, error handling, and app architecture.

Available from Ray Wenderlich: » Learn more.