Custom convenience operators with RxSwift, Part 2

Intro

I got some great feedback on last week’s post on convenience operators part 1 so I’m really excited to publish part 2, which I hope will be even more interesting for those of you who are looking into RxSwift.

Without further ado let’s dive in code…

A better negate() operator

First of all I have a better version of my negate() operator from last week for you. What I wrote on my own was a pretty simple function that looked like so:

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

It doesn’t get simpler than that, right? You map a value in a single line of code and that’s it (careful, it’s a trick question).

@tailec grabbed me in Slack and showed me his version of the same operator, which definitely beats mine:

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

Yup! Since ! is a function with a single parameter you can simply use in conjunction with map as shown above. That code definitely looks better than mine and I’m gonna be using this version in my own projects too. Thanks Pawel.

Please guys, if you see something I’m posting here that can be improved get in touch, like Pawel did, and help get some awesome rx code out together!

filterNegatives()

I was looking over my current project’s code and trying to identify repeating patterns I can easily outsource to a convenience operator.

I noticed I have few Bool observables that I am sometimes interested in only if they were emitting true value. For example if we take the code from my lap timer post:

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

Which produces depending on taps on the play and stop buttons:

true — false — true — true –>

How about observing only the taps on play? (Except just subscribing to the play button of course)

Since what I wanted was to basically get rid of the all false values I wrote:

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

Pretty easy piece of code, it does the work well and it’s clear to read. Great!

replaceNilWith(_)

This was an easy operator to implement. I had a couple of observables emitting optional values and I actually wanted to get a given default value instead of nil. I just had to map to a simple condition that checks for a nil value like so:

extension Observable {
  func replaceNilWith(value: Element) -> Observable<Element> {
     return map {element in element == nil ? value : element}
  }
}

Note that the elements of the observable are still of type Optional<Element> but you just never get a nil value - you get your default value instead.

filterOut(_)

While I was having a lot of momentum I decided to also write a filter that gets rid of specific values. Since the approach is exactly the same as before I’ll just add the code here:

extension Observable where Element: Equatable {
    public func filterOut(targetValue: Element) -> Observable<Element> {
        return self.filter {value in targetValue != value}
    }
}

The interesting aspect about this piece of code is that to be able to identify the offending value in your sequence it needs to be Equatable so you have to restrict the filterOut operator to only observables that emit Equatable elements.

This of course is a walk in the park in Swift where you can just add where Element: Equatable on your extension.

The best thing about filterOut(_) is that whenever I have an observable emitting optional values like for example Observable<Bool?>, I can ensure no nil values are emitted like so:

optionalBoolSequence.filterOut(nil)

The observable elements are still of type Optional<Bool> but now I am sure that the observable never emits a nil value.

And leads me to …

unwrap(_)

At that point I had two convenience operators to get rid of nils in my observables but the elements were still Optional.

Well, I thought, it could not be so hard to actually unwrap the elements of an Observable with all the Swift protocols black magic and such!

Boy, was I totally and completely wrong…

My first realization was that Optional is not a protocol. Thus I couldn’t do any protocol black magic with it. Ugh!

Optional is actually an enum. Yes - this sounded pretty exciting when Swift 1.0 alpha was out, but honestly right now I’d expect that it was a protocol or something more flexible.

Anywho, since Optional isn’t a protocol I couldn’t create an extension on Observable that matches optional elements. Gulp.

I had a long long conversation with Matthijs and Ross O’Brien in Slack until in the end I could figure out the way…

First of all I had to define a protocol for optionals myself. My protocol had to define two methods:

  • one that checks if the current value is nil (guess what - simply comparing self to nil didn’t really work, yay!)
  • another that unwraps self from Optional<Type> to Type

The big question that I had to wrap my mind around was: What type is the unwrapped value? I didn’t know that in my protocol so I had to define a type that the concrete implementations would set.

I ended up with:

protocol Optionable
{
  typealias WrappedType
  func unwrap() -> WrappedType
  func isEmpty() -> Bool
}

Cool - I had a protocol which I could use to add a method to Observable via an extension.

But first I had to make the Optional enum to conform to Optionable. So Optional exposes the type of the wrapped value via Wrapped and that’s where the magic fusion between Optional and my Optionable protocol happened.

Here’s the declaration of Optional:

public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {...}

And here’s how I wired Optionable to to the type of any concrete optional value:

extension Optional : Optionable
{
    typealias WrappedType = Wrapped
}

Ha! (Again Matthijs and Ross helped a lot along the way of untangling all of this. I must say that there is very little info online about protocol magic and associated types.)

Now I could also add the implementation of the two methods in the extension:

func unwrap() -> WrappedType {
    return self!
}

func isEmpty() -> Bool {
    return !(flatMap({_ in true})?.boolValue == true)
}

You guessed it - coding unwrap() was pretty straight forward, but isEmpty() caused me serious headache.

To my surprise (repeating myself here I know), Optional doesn’t give you means to check whether it’s empty or not.

At first I came with this naïve implementation:

func isEmpty() -> Bool {
  switch self {
    case .None: return false
    case .Some(_): return true
  }
}

Well, let me tell you: this doesn’t work. Although if you ask me it should. But it doesn’t - it never falls in the case .None branch for some reason and is beyond me why.

So I had to take the hard way, I looked again through everything I can find in an Optional:

It’s not much. Not much …

Wait! flatMap? But of course! Here’s the (complete) docs on Optional’s flatMap:

Returns nil if self is nil, f(self!) otherwise.

I rewrote my isEmpty() method to use flatMap and I was off to the races:

func isEmpty() -> Bool {
    return !(flatMap({_ in true})?.boolValue == true)
}

And now (finally) I could get to adding the extension to Observable. Compared to all the rest I’ve been through this was … let’s say not so difficult:

extension Observable where Element : Optionable {
  func unwrap() -> Observable<Element.WrappedType> {
    return self
      .filter {value in
        return !value.isEmpty()
      }
      .map {value -> Element.WrappedType in
        value.unwrap()
      }
  }
}

Notable mentions about that piece of code:

  • I’m matching the Observable type to Optionable. Optional implements Optionable but if any other type does that unwrap() will work for it too
  • unwrap() takes in an Element value and outputs Element.WrappedType, so for Int? outputs Int, for NSDate? outputs NSDate, etc.
  • why not use filterOut(nil) to get rid of the nil values? filterOut(_) works for Equatable values and Element.WrappedType might not be Equatable in some cases
  • I had to explicitly set the return type for my map closure because Xcode thought things were getting a bit too abstract for it

Now let’s see the complete implementation (and if you can think of ways to simplify this please let me know, I still think there should be an easier way):

protocol Optionable
{
  typealias WrappedType
  func unwrap() -> WrappedType
  func isEmpty() -> Bool
}

extension Optional : Optionable
{
  typealias WrappedType = Wrapped
  func unwrap() -> WrappedType {
    return self!
  }
    
  func isEmpty() -> Bool {
    return !(flatMap({_ in true})?.boolValue == true)
  }
}

extension Observable where Element : Optionable {
  func unwrap() -> Observable<Element.WrappedType> {
    return self
      .filter {value in
        return !value.isEmpty()
      }
      .map {value -> Element.WrappedType in
        value.unwrap()
      }
  }
}

Well that was a fun day! To be fair I also learned quite a lot about protocols, associated types, etc.

Later on I was talking to @fpillet who shared this piece here:

someOptionalSequence
  .flatMap { $0 == nil ? Observable.empty() : Observable.just($0!) }`

It’s a one liner you can use directly on your Observable of Optional<Element> type. It does pretty much the same as my unwrap() operator but it’s way shorter because the flatMap closure doesn’t need to specify its return type - it leaves it to Xcode to figure it out out of context.

I still like my own operator though - I think it’s much more readable and it adds less stress to the compiler to write just:

someOptionalSequence.unwrap()

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.

I’m already planning the next post: creating your own Cocoa bindings. If you have done some cool custom bindings for UIKit classes, or any other interesting bindable properties let me know. Woot!

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.
Tags// , ,