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!
Binder
moves from RxCocoa to RxSwiftwithUnretained
comes to RxSwift- Automatic synthesis of
Binder
s using@dynamicMemberLookup
Infallible
- New
decode(type:decoder:)
operator forObservable<Data>
- Variadic
drive()
andemit()
Single
now better follows Swift'sResult
- New
distinctUntilChange(at:)
operator for Key Paths - New
ReplayRelay
- New
DisposeBag
function 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 Binder
s 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 Binder
s 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 Observable
s 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 Driver
s and Signal
s - 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