DEV Community

Cover image for What's new in RxSwift 6 ?
Shai Mishali
Shai Mishali

Posted on • Edited on

What's new in RxSwift 6 ?

Happy new 2021! It's been quite a rough year, and I'm sure we're all hoping for a much better year where we'll finally be able to go to our normal lives.

To kick off the year, what's better than starting with a new release? Say hello to RxSwift 6.

This blog post will give you a quick rundown of some of the most note-worthy changes that might affect you.

Note: This is just a partial list of some interesting changes, that obviously don't include a massive amount of smaller bug fixes and improvements.

For the full change log, checkout the release notes.

Without further ado, let's just dive right in!

Table of Content

Changes

New logo!

Not a technical change, for sure, but definitely one worth mentioning.

RxSwift always used Reactive Extensions' original Volta Eel logo, but I felt that this major release can be a great opportunity to add a bit of a unique edge to RxSwift's logo.

An opportunity to make it unique with its own spirit and identity, in a way that still gives honor to the original ReactiveX logo as well as Swift's logo.

I give you, the new RxSwift logo!

ezgif-2-d5c1852097c8

Binder moves from RxCocoa to RxSwift

This is a small but highly requested change that just made sense. Binder, as the name suggests, lets you define a way to bind an Observable stream into it, to reactively feed that bound input.

For example:

viewModel.isButtonEnable.bind(to: myButton.rx.isEnabled)
Enter fullscreen mode Exit fullscreen mode

Uses an underlying Binder to let you bind into rx.isEnabled

Binder always lived inside RxCocoa, but use-cases by our community and various discussions showed that it is a super useful entity that serves the broader RxSwift audience, so it is now part of it and RxCocoa isn't required to use Binder.

Automatic synthesis of Binders using @dynamicMemberLookup

RxSwift includes a namespace called .rx which lets you put your own Reactive Extensions on it for specific objects.

For example, given a custom MyView that looks like this:

class MyView: UIView { 
    var title: String
    var subtitle: String?
    var icon: UIImage?
}
Enter fullscreen mode Exit fullscreen mode

A common pattern for creating reactive binders usually looks like this:

extension Reactive where Base: MyView {
    var title: Binder<String> {
       Binder(base) { base, title in 
           base.title = title
       }
    }

    var subtitle: Binder<String?> {
       Binder(base) { base, subtitle in 
           base.subtitle = subtitle
       }
    }

    var icon: Binder<UIImage?> {
       Binder(base) { base, icon in 
           base.icon = icon
       }
    }
}
Enter fullscreen mode Exit fullscreen mode

This would let you bind an observable stream of the appropriate type to the various reactive inputs:

viewModel.title.bind(to: myView.rx.title)
viewModel.subtitle.bind(to: myView.rx.subtitle)
viewModel.icon.drive(myView.rx.icon)
Enter fullscreen mode Exit fullscreen mode

This works great, and is even how RxCocoa itself provides reactive binders for its consumers.

Unfortunately, this sort of code is also quite repetitive and boilerplate-y. All it really does is mirror the underlying Base's properties.

Fortunately, since Swift 5.1, we have a better solution for this problem - @dynamicMemberLookup.

RxSwift 6 will automatically synthesize all of these Binders for any class, which means that all of the Binder code I showed above can be entirely removed, and really clean up your code.

Just start writing .rx on any AnyObject-inheriting class, and you'll immediately see automatically synthesized binders for every property of the extended base object:

Automatically synthesized binders for all properties of MyView

Don't worry though, your own custom reactive extensions still take precedence over the synthesized dynamic member ones, which lets you have more granular control.

withUnretained comes to RxSwift

A common pattern when working with RxSwift and Cocoa / iOS code is to get a weak reference to self so you could pass an emitted value to the owner, for example:

viewModel.importantInfo
    .subscribe(onNext: { [weak self] info in 
        guard let self = self else { return }
        self.doImportantTask(with: info)
    })
    .disposed(on: disposeBag)
Enter fullscreen mode Exit fullscreen mode

Note: Be careful using this operator with a buffering operator such as share(replay: 1) as it would also buffer the retained object, which might cause a retain cycle. If you want a simpler alternative to this, check out subscribe(with:onNext:onError:onCompleted:onDisposed:) from RxSwift 6.1.

This might seem fine for a single output, but imagine how frequently this pops in a single code base.

Luckily RxSwiftExt, a community project that holds various additional operators that aren't part of RxSwift itself, has an operator for this very case, called withUnretained. It was initially implemented by a good friend and fellow iOS speaker, Vincent Pradeilles.

Due to the popularity of this operator, and how common this use case is, it made sense to bring it into RxSwift itself.

Starting with RxSwift 6, you can rewrite the above code like so:

viewModel.importantInfo
  .withUnretained(self) // Tuple of (Object, Element)
  .subscribe(onNext: { owner, info in 
    owner.doImportantTask(with: info)
  })
  .disposed(by: disposeBag)
Enter fullscreen mode Exit fullscreen mode

Much cleaner!

Infallible

Infallible is a new type of stream that is identical to Observable with only one difference โ€” it is guaranteed to not fail. This means you cannot emit errors from it, guaranteed by the compiler.

For example, you can create one similarly to Observable.create, using Infallible.create:

Infallible<String>.create { observer in
    observer(.next("Hello"))
    observer(.next("World"))
    observer(.completed)
    // No way to error here

    return Disposables.create {
        // Clean-up
    }
}
Enter fullscreen mode Exit fullscreen mode

Note that you can only pass a .next(Element) or .completed event. There is no way for you to fail this stream. All other operators that work around Infallible have the same guarantee (for example, you cannot call Infallible.error as opposed to Observable.error)

If you worked with RxCocoa, you're probably thinking โ€” hey, what's the difference between this, Driver, and Signal?

First of all, Infallible lives inside RxSwift, while the other two live in RxCocoa. But more importantly, both Driver and Signal always use the MainScheduler and share their resources (using share()). This isn't the case with Infallible which is entirely a basic observable, only with compile-time guarantee for infallibility.

New decode(type:decoder:) operator for Observable<Data>

RxSwift 6 adds a decode operator that specifically works on Observables that emit Data, similarly to Combine's:

service.rx
       .fetchJSONUsers() // Observable<Data>
       .decode(type: [User].self, decoder: JSONDecoder()) // Observable<[User]>
Enter fullscreen mode Exit fullscreen mode

Variadic drive() and emit()

RxSwift 5 introduced variadic bind, which lets you do:

viewModel.string.bind(to: input1, input2, input3)
Enter fullscreen mode Exit fullscreen mode

RxSwift 6 now brings the same variadic binding for Drivers and Signals - using variadic drive and emit operators:

viewModel.string.drive(input1, input2, input3)
viewModel.number.emit(input4, input5)
Enter fullscreen mode Exit fullscreen mode

Single now better follows Swift's Result

Up until RxSwift 5, Single had a custom event:

public enum SingleEvent<Element> {
    case success(Element)
    case error(Swift.Error)
}
Enter fullscreen mode Exit fullscreen mode

If you're looking at this and saying, hey - this looks a lot like a Result<Element, Swift.Error>, you're absolutely right!

Starting with RxSwift 6, SingleEvent is simply an alias for Result<Element, Swift.Error>.

This change is also reflected in other APIs such as subscribe:

// RxSwift 5
single.subscribe(
    onSuccess: { value in
        print("Got a value: \(value)")
    },
    onError: { error in
        print("Something went wrong: \(error)")
    }
)

// RxSwift 6
single.subscribe(
    onSuccess: { value in
        print("Got a value: \(value)")
    },
    onFailure: { error in
        print("Something went wrong: \(error)")
    }
)
Enter fullscreen mode Exit fullscreen mode

New distinctUntilChange(at:) operator for Key Paths

distinctUntilChanged is a super useful operator which lets you drop identical value emissions to avoid wastefully processing them.

For example:

myStream.distinctUntilChanged { $0.searchTerm == $1.searchTerm }
Enter fullscreen mode Exit fullscreen mode

This is another case where Key Paths can prove quite useful. Starting with RxSwift 6, you can simply write:

myStream.distinctUntilChanged(at: \.searchTerm)
Enter fullscreen mode Exit fullscreen mode

New ReplayRelay

Relays wrap around subjects, and let you relay messages in a way that only handles values, since relays are guaranteed to never fail or complete.

ReplayRelay is the latest addition to RxSwift 6, which wraps ReplaySubject, in addition to the existing BehaviorRelay and PublishRelay.

Creating one uses the exact same interface as creating a ReplaySubject:

// Subject
ReplaySubject<Int>.create(bufferSize: 3)

// Relay
ReplayRelay<Int>.create(bufferSize: 3)
Enter fullscreen mode Exit fullscreen mode

New DisposeBag function builder

RxSwift 6 includes a new DisposeBag function builder for SwiftUI-like, comma-less syntax:

var disposeBag = DisposeBag { 
    observable1.bind(to: input1)

    observable2.drive(input2)

    observable3.subscribe(onNext: { val in 
        print("Got \(val)")
    })
}

// Also works for insertions
disposeBag.insert {
    observable4.subscribe()

    observable5.bind(to: input5)
}
Enter fullscreen mode Exit fullscreen mode

Many, many (many) operator renames

We took the time in RxSwift 6 and renamed many operators to better respect Swift's code guidelines where possible.

Here is a mostly complete list:

RxSwift 5 RxSwift 6
catchError(_:) catch(_:)
catchErrorJustReturn(_:) catchAndReturn(_:)
elementAt(_:) element(at:)
retryWhen(_:) retry(when:)
takeUntil(_:) take(until:)
takeUntil(behavior:_:) take(until:behavior:)
takeWhile(_:) take(while:)
takeWhile(behavior:_:) take(while:behavior:)
take(.seconds(3)) take(for: .seconds(3))
skipWhile(_:) skip(while:)
takeUntil(_:) take(until:)
observeOn(_:) observe(on:)
subscribeOn(_:) subscribe(on:)

Better support for XCFrameworks

Every release of RxSwift 6 will now have a set of XCFrameworks bundled with the release.

This allows easily linking against a prebuilt copy of RxSwift without worrying about forward compatibility when upgrading to the next version of Swift, thanks to binary module stability.

Wrapping up!

Hope you've enjoyed this quick rundown of some of the most interesting features and updates to RxSwift 6, but it's not all that was fixed.

There are a ton of bug fixes, improvements and small additions that are worth checking out. Be sure to take a moment to review the release notes.

GitHub logo ReactiveX / RxSwift

Reactive Programming in Swift

RxSwift Logo
Build Status Supported Platforms: iOS, macOS, tvOS, watchOS & Linux

Rx is a generic abstraction of computation expressed through Observable<Element> interface, which lets you broadcast and subscribe to values and other events from an Observable stream.

RxSwift is the Swift-specific implementation of the Reactive Extensions standard.

RxSwift Observable Example of a price constantly changing and updating the app's UI

While this version aims to stay true to the original spirit and naming conventions of Rx, this project also aims to provide a true Swift-first API for Rx APIs.

Cross platform documentation can be found on ReactiveX.io.

Like other Rx implementations, RxSwift's intention is to enable easy composition of asynchronous operations and streams of data in the form of Observable objects and a suite of methods to transform and compose these pieces of asynchronous work.

KVO observation, async operations, UI Events and other streams of data are all unified under abstraction of sequence. This is the reason why Rx is so simple, elegant and powerful.

I came here because I want to

โ€ฆ

See you on the next blog post, and hope you enjoy RxSwift 6!

Top comments (1)

Collapse
 
makhovyk profile image
mike.makhovyk

Great updates! Kind of sucks that I can't use withUnretaned with Single