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
- New logo!
Bindermoves from RxCocoa to RxSwiftwithUnretainedcomes to RxSwift- Automatic synthesis of
Binders using@dynamicMemberLookup Infallible- New
decode(type:decoder:)operator forObservable<Data> - Variadic
drive()andemit() Singlenow better follows Swift'sResult- New
distinctUntilChange(at:)operator for Key Paths - New
ReplayRelay - New
DisposeBagfunction builder - Many, many (many) operator renames
- Better support for XCFrameworks
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!
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)
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?
}
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
}
}
}
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)
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:
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)
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 outsubscribe(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)
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
}
}
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]>
Variadic drive() and emit()
RxSwift 5 introduced variadic bind, which lets you do:
viewModel.string.bind(to: input1, input2, input3)
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)
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)
}
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)")
}
)
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 }
This is another case where Key Paths can prove quite useful. Starting with RxSwift 6, you can simply write:
myStream.distinctUntilChanged(at: \.searchTerm)
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)
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)
}
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.
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.
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)
Great updates! Kind of sucks that I can't use withUnretaned with Single