You may have heard about reactive programming, and maybe even checked out RxSwift. But if you’re not using it in your daily development, you’re really missing out! During this talk at AltConf 2016 Scott Gardner introduces you into the world of reactive programming, giving you an easy way to start off learning Reactive Swift. He takes a look into RxSwift while also giving you many other resources that will guide your learning. Come see how reactive programming with RxSwift will change your life and make you richer than your wildest dreams.
Introduction (0:00)
I’m really excited to talk about reactive programming today, and specifically about RxSwift. Before I get started, I would just like to talk a little bit about myself, tell you who I am.
My name’s Scott Gardner, I’ve been developing in iOS for about six years, and in Swift for two years. I wrote one of the first books on Swift that helped Objective-C developers transition to Swift. I’ve taught and written tutorials and presented on Swift and iOS at CocoaConf and some meetups, at Washington University in St. Louis, written some articles for Ray Wenderlich, and I’m currently an author at Lynda.com. I also am a contributor to the RxSwift project, and as Alex mentioned, I’m starting later this month at The Weather Channel, which is an IBM company, as the manager of the iOS engineering team.
Today’s talk will be about using a reactive library called RxSwift, which will do a lot of heavy lifting for you and just make your life easier as a developer.
Traditional vs. Reactive (1:36)
I thought I’d start out by jumping right in to an example just to give you a feel for what RxSwift can do for you.
To set up what’s going on here, this is an iOS app, and I’m starting out with a speaker struct, which is just a basic data capture of name, Twitter handle, and an image, and it conforms to custom string convertible.
import UIKit
struct Speaker {
let name: String
let twitterHandle: String
var image: UIImage?
init(name: String, twitterHandle: String) {
self.name = name
self.twitterHandle = twitterHandle
image = UIImage(named: name.stringByReplacingOccurrencesOfString(" ", withString: ""))
}
}
extension Speaker: CustomStringConvertible {
var description: String {
return "\(name) \(twitterHandle)"
}
}
Next, we have a ViewModel
.
import Foundation
struct SpeakerListViewModel {
let data = [
Speaker(name: "Ben Sandofsky", twitterHandle: "@sandofsky"),
Speaker(name: "Carla White", twitterHandle: "@carlawhite"),
Speaker(name: "Jaimee Newberry", twitterHandle: "@jaimeejaimee"),
Speaker(name: "Natasha Murashev", twitterHandle: "@natashatherobot"),
Speaker(name: "Robi Ganguly", twitterHandle: "@rganguly"),
Speaker(name: "Virginia Roberts", twitterHandle: "@askvirginia"),
Speaker(name: "Scott Gardner", twitterHandle: "@scotteg")
]
}
And I don’t want to make this a talk about MVVM or architectural patterns like that. All I’m really doing here is just taking the creation of the data that’s going to be used by a UITableView
, and extracting that to a separate type that will create that data. Then it will be used by the view controller that controls the UITableView
, so just really compartmentalizing this code a little bit. Then in the view controller, I’m implementing the standard UITableViewDataSource
and UITableViewDelegate
protocols as we’ve all done countless times. It’s really boilerplate code, you write it over and over and over again. And that gives us a nice UITableView
listing some of the speakers here this week.
class SpeakerListViewController: UIViewController {
@IBOutlet weak var speakerListTableView: UITableView!
let speakerListViewModel = SpeakerListViewModel()
override func viewDidLoad() {
super.viewDidLoad()
speakerListTableView.dataSource = self
speakerListTableView.delegate = self
}
}
extension SpeakerListViewController: UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int)-> Int {
return speakerListViewModel.data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCellWithIdentifier("SpeakerCell")
else {
return UITableViewCell()
}
let speaker = speakerListViewModel.data[indexPath.row]
cell.textLabel?.text = speaker.name
cell.detailTextLabel?.text = speaker.twitterHandle
cell.imageView?.image = speaker.image
return cell
}
}
extension SpeakerListViewController: UITableViewDelegate {
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You selected \(speakerListViewModel.data[indexPath.row])")
}
}
So now I’ve Rxified this project, and the first thing I did was I modified the ViewModel
to make its data property an observable sequence. And I’m going to explain what that is, but basically it’s an observable sequence that wraps the same exact values we had before in that array. Sequences can subscribed to it, similar in concept to the way NSNotificationCenter
works.
import RxSwift
struct SpeakerListViewModel {
let data = Observable.just([
Speaker(name: "Ben Sandofsky", twitterHandle: "@sandofsky"),
Speaker(name: "Carla White", twitterHandle: "@carlawhite"),
Speaker(name: "Jaimee Newberry", twitterHandle: "@jaimeejaimee"),
Speaker(name: "Natasha Murashev", twitterHandle: "@natashatherobot"),
Speaker(name: "Robi Ganguly", twitterHandle: "@rganguly"),
Speaker(name: "Virginia Roberts", twitterHandle: "@askvirginia"),
Speaker(name: "Scott Gardner", twitterHandle: "@scotteg")
])
}
So in the view controller, instead of implementing the data source and delegate protocols, I’ve written some reactive code here that binds the UITableView
to the data.
import RxSwift
import RxCocoa
class SpeakerListViewController: UIViewController {
@IBOutlet weak var speakerListTableView: UITableView!
let speakerListViewModel = SpeakerListViewModel()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
speakerListViewModel.data
.bindTo(speakerListTableView.rx_itemsWithCellIdentifier("SpeakerCell")) { _, speaker, cell in
cell.textLabel?.text = speaker.name
cell.detailTextLabel?.text = speaker.twitterHandle
cell.imageView?.image = speaker.image
}
.addDisposableTo(disposeBag)
speakerListTableView.rx_modelSelected(Speaker)
.subsribeNext { speaker in
print("You selected \(speaker)")
}
.addDisposableTo(disposeBag)
}
}
I’ll just walk through this code briefly.
I’ve added this thing called the dispose bag, and this is one way that Rx can dispose of subscriptions for you when you are about to de-init, when the view controller or the owner is about to de-init. And this is kind of similar to NSNotificationCenter
’s RemoveObserver
.
So subscriptions will be disposed of on de-init, and it takes care of freeing up those resources. Next we have this rx_itemsWithCellIdentifier
, and that’s an Rx wrapper around the cellForRowAtIndexPath
data source method. And also Rx takes care of the implementation of the numberOfRowsAtIndexPath
, which is a required method in a traditional sense, but you don’t have to implement it here, it’s taken care of.
Next is this rx_modelSelected
, which is an Rx wrapper on the UITableView
delegate callback didSelectRowAtIndexPath
.
So that’s it. Let’s compare the traditional to the reactive.
Results of Traditional vs Reactive (4:07)
It actually amounts to about 40% less code that you have to write to do the exact same thing. To put that in perspective, that’s kind of like being able to work until just after lunch and getting paid for a full day’s work. Or, for those of us here in the States, working until the 4th of July and then taking the rest of the year off with the same salary.
That sounds good.
This is just a simple example, but it’s not just about writing less code. It’s about writing more expressive code and doing more with that code, especially when it comes to writing asynchronous code.
What is Reactive Programming? (4:45)
So you may be wondering what is reactive programming?
Reactive programming is, at its essence, about modeling asynchronous observable sequences. And those sequences can contain values, like I just showed you in that last example, but they can also be sequences of events, like taps or other gestures. Everything else builds upon that concept. So reactive programming’s been around for about two decades, but it’s just been picking up a lot of momentum in the last few years with the introduction of reactive extensions in 2009.
So RxSwift is a collection of the standard operators that exist across every implementation of Rx, and it can be used to create and work with observable sequences.
There are also platform-specific libraries, such as RxCocoa that I showed you in the last example, that are specific to iOS or OS X and that sort of thing. RxSwift is the official implementation of reactive extensions for Swift, and each of these implementations implements the exact same patterns and pretty much the exact same operators across all of these different languages and technology platforms.
Sequences Everywhere (5:58)
In Rx, pretty much everything is either an observable sequence or it’s something that works with an observable sequence. So a sequence emits things, and they’re technically events. You can subscribe to an observable sequence to react to those events as they’re emitted. Again, this is kind of like NSNotificationCenter
a little bit, but Rx just sort of takes it up a notch or two.
RxSwift operators perform various tasks, and they’re based on events. It’s typically done in an asynchronous fashion, and Rx is functional, so you might think that that means this is functional reactive programming or FRP, and I’m fine with that, I kind of think that it is. Others may not.
Rx Patterns (5:19)
So Rx implements two common patterns:
-
The first is observer, and that is the subject maintains a list of its dependents called observers or subscribers, and it notifies them of changes.
-
And then iterator, which is really just how collections or sequences of values can be traversed.
Rx exists for pretty much every modern language. So, let me talk a little bit about the lifecycle of an observable sequence.
Observable Sequence Lifecycle (7:25)
This arrow represents an observable sequence over time, and when a value or a collection of values is put onto the sequence, it’s going to emit a next event to its observers that will contain that element or collection of elements in that next event. Again, this is known as emitting, and the values are referred to as elements. This works the same way whether we’re talking about values as we see here, or tap events, like touch events, TouchUpInside
events type of thing. (By the way, this is called a marble diagram.)
If an error is encountered, a sequence can emit an error event, and that’ll contain an error type instance, and you can then react to that event to do some error handling, interrogate that error, and see what’s going on. When an error occurs, which is represented by the X on the timeline, it also terminates the sequence, and when it’s terminated, it can no longer emit any more events, so once you get an error event, that’s it.
A sequence can also terminate normally, and when it does that, it’s going to emit a completed event, and that’s represented by this vertical bar at the end of the timeline there.
There we go.
So I’ve been saying RxSwift a lot, and really, when I say RxSwift, I’m really referring to a much larger set of capabilities that includes the RxSwift core library, but also RxCocoa, which is a platform-specific implementation of reactive extensions for iOS, OS X, watchOS, and tvOS. There’s a collection of reactive libraries that are made available in the RxSwift community repo. That includes, for example, RxDataSources library. That provides some additional reactive extensions for working with UITableView
s and CollectionView
s in more advanced ways.
Follow Along (9:50)
If you wanted to follow along, you could use follow the instructions below to get the CocoaPods Playgrounds plugin, which will create a Playground that you can choose one or more libraries like RxSwift to pull in.
- Install ThisCouldBeUsButYouPlaying
- bit.ly/podsPlaygrounds
gem install cocoapods-playgrounds
- Create RxSwiftPlayground
pod playgrounds RxSwift
The first thing I’ve done is just created a helper function that’s going to wrap each of my examples. It’s gonna print a header, and then it’s going to execute the example’s code.
//Please build the scheme 'RxSwiftPlayground' first
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
import RxSwift
func exampleOf(description: String, action: Void -> Void) {
print("\n--- Example of:", description, "---")
action()
}
Simple enough.
Creating and Subscribing to Observable Sequences (10:00)
This first example is using the just
operator, which will create an observable sequence of a single value, just an integer in this case. Then I’m going to subscribe to events that are going to be emitted from this observable sequence using this subscribe operator. And as I receive each event as it’s emitted, I’ll just print it out, and I’m just using the $0
default argument name.
exampleOf("just") {
Observable.just(32)
.subscribe {
print($0)
}
}
So first we get this next event containing the element, and then we get a completed event. And like with the error, once an observable sequence has emitted a completed event, it’s terminated. It can’t emit any more elements.
exampleOf("just") {
_ = Observable.just(32)
.subscribeNext {
print($0)
}
}
For those that aren’t as familiar with Swift, this $0
is just a default argument. If I wanted to, I could have actually named it explicitly like this, but I tend to use the default argument names, $0
, $1
, etc., when there’s one or two arguments for a closure. Anything more than that I usually spell it out.
If you see a warning in your Playground at this point, it would be because subscribe actually returns a value that represents the subscription, which is of a type that’s called disposable, and we haven’t done anything with that yet.
exampleOf("just") {
Observable.just(32)
.subscribe { element in
print(element)
}
}
I’m gonna slightly modify this example first of all, just to explicitly ignore that value. And then notice I use this subscribeNext
operator this time. subscribeNext
only listens for next events, and it returns just the element, not the event containing the element. So now we only get the element printed out.
exampleOf("just") {
_ = Observable.just(32)
.subscribeNext {
print($0)
}
}
Observable Sequence from Multiple Values (11:34)
Next I’m gonna look at how to create an observable sequence from multiple values. So this of
operator can take a variable number of values. I subscribe to it and print it out just like before, only this time, instead of explicitly ignoring the return value, I’m gonna call dispose on the subscription, and dispose cancels the subscription. So now we get a print out of each element from the observable sequence.
exampleOf("of") {
Observable.of(1, 2, 3, 4, 5)
.subscribeNext {
print($0)
}
.dispose()
}
We can also create observable sequences from arrays of values. toObservable
will convert an array` into an observable sequence.
So first I’ll just create a dispose bag, which remember will dispose of its contents on deinit, and then I’ll use toObservable
to convert the array of integers to an observable sequence, and then I’ll subscribe next and print out the elements. Then I’ll add that subscription to the dispose bag.
exampleOf(){
let disposeBag = DisposeBag()
[1, 2, 3].toObservable()
.subscribeNext {
print($0)
}
.addDisposableTo(disposeBag)
}
Simple as that.
Modifiable Observable Sequence (12:32)
Usually, though, you’re going to want to create an observable sequence that you can add new values on to, and then have subscribers receive next events containing those newly added values, ongoing in an asynchronous way throughout your app’s lifecycle. A BehaviorSubject
is one way to do that. You can think of a BehaviorSubject
is simply representing a value that is updated or changed over time.
I’m gonna explain why that’s not exactly the case in just a moment, but first, I’m gonna just create a string BehaviorSubject
here with an initial value of “Hello.” And then I’ll subscribe to it to receive updates and print them out. And notice that I didn’t use the subscribeNext
this time. You’ll see why in a moment. Then I add this new value “World” onto the string BehaviorSubject.
exampleOf("BehaviorSubject") {
let disposeBag = DisposeBag()
let string = BehaviorSubject(value: "Hello")
string.subscribe {
print($0)
}
.addDisposableTo(disposeBag)
string.on(.Next("World!"))
}
Like I said a moment ago, BehaviorSubject
represents a value that can change over time, but I’m not actually changing the value. This is immutable. What I’m doing is I’m adding this “World” value onto the sequence, using this on operator, with a next case that wraps that new value. This will then cause the sequence to emit a next event to its observers. So this on and then case next is sort of the original way to use that operator to put these new values onto the sequence, but there’s also a convenience operator that I tend to use pretty much all the time, and that’s onNext
. And it does the exact same thing, it’s just a little bit easier to write, and a little bit easier to read after the fact.
As a result, I see both next events with the elements printed out, including the initial value, because when it was assigned before the subscription even existed. That’s because a BehaviorSubject
will always emit the latest or the initial element when new subscribers subscribe to it, so you always get that replay of that last item. A BehaviorSubject
does not automatically emit a completed event, though, when it’s about to be disposed of, and it also can emit an error event and terminate. We may not want that. Typically, you want the completed event to be automatically emitted when the subscription’s gonna be disposed of, but you want to prevent error events from being emitted, if we’re talking about UI integration, that kind of thing.
So as an alternative, you can use a type called Variable
. Variable
is a wrapper around BehaviorSubject
. It exposes its BehaviorSubject
’s observable sequence via the asObservable
operator. And what a Variable
does that a regular BehaviorSubject
will not, is a Variable
is guaranteed to not emit error events, and it will automatically emit a completed event when it’s about to be disposed of. So use of Variable
’s value property to get the current value or to set a new value onto the sequence. I like the syntax a little better than using that onNext
so I tend to use Variable
s a lot. For some other reasons too.
exampleOf("Variable") {
let disposeBag = DisposeBag()
let number = Variable(1)
number.asObservable()
.subscribe {
print($0)
}
.addDisposableTo(disposeBag)
number.value = 12
number.value = 1_234_567
}
These are the basic building blocks of reactive programming in RxSwift, and there are a few more types, and of course lots more operators. I’ll go over some more in a moment. But once you get the basic gist of this, everything else is a variation. It should come pretty easy. Again, if you remember just one thing from this talk, everything is a sequence. You’re not changing a value. You’re putting new values onto the sequence, and then observers can react to those values as they’re emitted by the observable sequence.
Transform Observable Sequences (15:38)
I’ve gone over some ways that you can create observable sequences, now I’m going to cover just a few ways that you can transform observable sequences. So you know about Swift’s map method, right?
You can take an array and you can call map on an array, pass it closure that provides some sort of transformation, and then that will return an array of all those elements transformed. Rx has its own map operator that works very similarly.
Here I’m gonna just create an observable sequence of integers using the of operator. Then I use map to transform them by multiplying each element by itself, and then I use subscribeNext to just print out each element.
exampleOf("map") {
let disposeBag = DisposeBag()
Observable.of(1, 2, 3)
.map { $0 * $0 }
.subscribeNext { print($0) }
.addDisposableTo(disposeBag)
}
So that’s map.
In a marble diagram, it would look like this.
Map will transform each element as it’s emitted from the source observable sequence. So what happens when you want to subscribe to an observable sequence that has observable sequence properties that you wanna listen to? So observable sequence that has observable sequence properties.
Down the rabbit hole. RxSwift’s flatMap
is also conceptually similar to Swift’s standard flatMap
method, except that, of course, it works with observable sequences and it does so in an asynchronous manner. It will flatten an observable sequence of observable sequences into a single sequence. flatMap
will also apply a transforming action to the elements that are emitted by that observable sequence.
So I’m gonna walk step by step through an example here:
First, we have this Person
struct, and it has a name variable of type Variable
of string. So name contains an observable sequence, but because I declared it as var
, so it not only can have values added onto the sequence, but the sequence itself can be reassigned.
exampleOf("flatMap") {
let disposeBag = DisposeBag()
struct Person {
var name: Variable<String>
}
let scott = Person(name: Variable("Scott"))
let lori = Person(name: Variable("Lori"))
let person = Variable(scott)
person.asObservable()
.flatMap {
$0.name.asObservable()
}
.subscribeNext {
print($0)
}
.addDisposableTo(disposeBag)
person.value = lori
scott.name.value = "Eric"
}
This is a contrived example, just to kind of show you the inner workings here. So then I create a couple of instances of Person
, Scott and Lori. And then I’ll use flatMap
to reach into the Person
observable and access its name observable sequence, and then only print out newly added values.
Remember with Variables
, you use the asObservable
to access its underlying observable sequence. So Person
starts out with Scott, and Scott is printed. But then I reassign Person
’s value altogether to Lori, so Lori is printed. And then I’ll put a new value on Scott’s sequence.
Because I used flatMap
, both sequences are actually still active, so we will see Eric printed out.That could be a gotcha. This could be sort of like a memory leak, if you think you should be using flatMap
when you should be using something else. That something else is flatMapLatest
. So this flatMapLatest
operator, if I use it here instead, will also flatMap
, but then it will also switch the subscription to the latest sequence, and it’s gonna ignore emissions from the previous one.
exampleOf("flatMapLatest") {
let disposeBag = DisposeBag()
struct Person {
var name: Variable<String>
}
let scott = Person(name: Variable("Scott"))
let lori = Person(name: Variable("Lori"))
let person = Variable(scott)
person.asObservable()
.flatMapLatest {
$0.name.asObservable()
}
.subscribeNext {
print($0)
}
.addDisposableTo(disposeBag)
person.value = lori
scott.name.value = "Eric"
}
So because Scott’s emissions will be ignored once I set person.value
to Lori, Eric is not printed out when I set it on scott.name
. So really, this is sort of like a synchronous example just to show you the inner workings of flatMapLatest
, but this is really more often used asynchronously with things like networking operations, and I’m going to go over an example of that here shortly.
Debug (18:59)
RxSwift also includes a couple of really useful debugging operators. One of them is called debug
. And you can add debug
to a chain like this.
exampleOf("flatMapLatest") {
let disposeBag = DisposeBag()
struct Person {
var name: Variable<String>
}
let scott = Person(name: Variable("Scott"))
let lori = Person(name: Variable("Lori"))
let person = Variable(scott)
person.asObservable()
.debug("person")
.flatMapLatest {
$0.name.asObservable()
}
.subscribeNext {
print($0)
}
.addDisposableTo(disposeBag)
person.value = lori
scott.name.value = "Eric"
}
You can optionally include a description string, and then it will print out detailed information about every event received.Really useful if you’re just getting started trying to figure out how all this stuff is working.
There are quite a few more operators in RxSwift. I would like to touch upon just a few more before I move on here, and I’m gonna go over these ones a little more quickly, but I’ll cover what I can, and then I’ll give some great resources that we can go back to later if you’d like to learn more.
Filtering (19:28)
Here’s an example of using distinctUntilChanged
, which is going to be used to suppress consecutive duplicates, so if the value is the same as the previous value, the latest value on that sequence it’s going to ignore. searchString
starts out with this value iOS, and then I’ll use map to transform the string to a lowercase string, and then I’ll use distinctUntilChanged
to filter out and ignore new values that are added onto that sequence that are the same as the latest value. Then I just subscribe to print it out.
exampleOf("distinctUntilChanged") {
let disposeBag = DisposeBag()
let searchString = Variable("iOS")
searchString.asObservable()
.map { $0.lowercaseString }
.distinctUntilChanged()
.subscribeNext { print($0) }
.addDisposableTo(disposeBag)
searchString.value = "IOS"
searchString.value = "Rx"
searchString.value = "ios"
}
The sequence was created with an initial value of iOS, so that initial value was printed out upon subscription, and then I added the same value onto the sequence. And even though it’s differently cased, it will be ignored because of distinctUntilChanged
. So then I add a new value of Rx onto the sequence, and that’s printed, and then I add ios back onto the sequence again, and this time, it is printed, because it’s not equal to the latest value.
Combining (20:24)
Sometimes you want to observe multiple sequences at the same time. And for that we can use this combineLatest
operator, which combines multiple sequences into a single sequence. You might be thinking back to the flatMapLatest
operator I showed you a couple minutes ago. flatMapLatest
is actually a combination of the map and the combineLatest
operators. And switchLatest
operators.
Here’s another Rx subject type, it’s called PublishSubject
, and it’s similar to a behavior subject, except for it doesn’t require an initial value, and it won’t replay the latest value to new subscribers. That’s the difference between PublishSubject
and BehaviorSubject
.
Then I’ll use combineLatest
, which will wait for each source observable sequence to emit an element before it produces any next event. But then once they both do emit a next event, then it’ll emit a next event whenever any of the source observable sequences emits an event. Because I’m at the point where I just had that circle show up, that we haven’t put a value on the number sequence yet, string does not have a value yet. So the subscription does not produce anything yet.
And then just “Nothing yet” is printed out at this point. As soon as I do add a value onto the string sequence, the latest elements from both the string and the number sequences are printed out, and then from that point forward, whenever either of the source sequences emits a new element, the latest elements from both sequences will be printed out.
exampleOf("combineLatest") {
let disposeBag = DisposeBag()
let number = PublishSubject<Int>()
let string = PublishSubject<String>()
Observable.combineLatest(number, string) { "\($0) \($1)" }
.subscribeNext { print($0) }
.addDisposableTo(disposeBag)
number.onNext(1)
print("Nothing yet")
string.onNext("A")
number.onNext(2)
string.onNext("B")
string.onNext("C")
}
Conditional (21:49)
Here’s an example of takeWhile
, which takes emitted elements only as long as a specified condition is true.
First, I’ll just convert an array of integers to observable, then I use takeWhile
to conditionally only take elements as long as each element is less than five. Once an element is five or greater, then it stops and the sequence is terminated and emits a completed message.
exampleOf("takeWhile") {
[1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1].toObservable()
.takeWhile { $0 < 5 }
.subscribeNext { print($0) }
.dispose()
}
Mathematical (22:33)
RxSwift has a reduce operator that works the same way as the reduce method in the Swift standard library, but sometimes you may want to work with the intermediate values along the way, and for that, RxSwift has the scan
operator.
exampleOf("scan") {
Observable.of(1, 2, 3, 4, 5)
.scan(10, accumulator: +)
.subscribeNext { print($0) }
.dispose()
}
So scan
will accumulate a sequence of values, starting with an initial value, and then return each intermediate value. And just like the reduce operator, scan
can take a regular operator, such as the plus, or you can actually pass it a closure of the same function type.
exampleOf("scan") {
Observable.of(1, 2, 3, 4, 5)
.scan(10) { $0 + $1 }
.subscribeNext { print($0) }
.dispose()
}
Error Handling (22:11)
So remember that some observable sequences can emit an error event, so this is just a real simple example demonstrating how you can subscribe to handle an error event.
I’ve created an observable sequence that terminates immediately with an error. Not very useful. But then I use the subscribeError
operator so that I can handle the error, which in this example I just print out.
exampleOf("error") {
enum Error: ErrorType { case A }
Observable<Int>.error(Error.A)
.subscribeError {
// Handle error
print($0)
}
.dispose()
}
Networking (22:59)
So let me show you that networking example using Rx extensions. This is a basic single view app with a UITableView
, and there’s a repository model that’s just a struct with a name
and url
string properties.
struct Repository {
let name: String
let url: String
}
And in the view controller, I’ve created a search controller and I’ve configured it and then added the search bar to the UITableView
’s header.
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
let searchController = UISearchController(searchResultsControler: nil)
var searchBar: UISearchBar { return searchController.searchBar }
var viewModel = ViewModel()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
configureSearrchController()
searchBar.rx_text
.bindTo(viewModel.searchText)
.addDisposableTo(disposeBag)
searchBar.rx_cancelButtonClicked
.map{ "" }
.bindTo(viewModel.searchText)
.addDisposableTo(sidposeBag)
viewModel.data
.drive(tableView.rx_itemsWithCellIdentifier("Cell")) { _, repository, cell in
cell.textLabel?.text = repository.name
cell.detailTextLabel?.text = reopsitory.url
}
.addDisposableTo(disposeBag)
}
func configureSearchController() {
searchController.obscureBackgroundDuringPresentation = false
searchBar.showsCancelButton = true
searchBar.text = "scotteg"
searchBar.placeholder = "Enter GitHub Id, e.g., \"scotteg\""
tableView.tableHeaderView = searchController.searchBar
definePresentationContext = true
}
}
I’m binding the search bar’s Rx text, which is an Rx wrapper around UISearchBar
’s text property, to the ViewModel
’s search text observable sequence.
I’m also binding the search bar’s rx_cancelButtonTapped
to the ViewModel
’s search text sequence too. And I’m using map to put an empty string onto the sequence when the cancel button’s tapped. rx_cancelButtonTapped
is an Rx wrapper around UISearchBarDelegate
’s searchBarCancelButtonClicked
callback. And finally, I’m binding the ViewModel’s data sequence to the UITableView
, just like I did in my first example. So that’s it for the view controller. Let me look at the ViewModel
now. In a production app, I’d probably compartmentalize this networking code into a separate API manager, but I thought it would be easier just to have it all on one screen to kind of go through it.
There’s this search text variable, which remember, wraps an observable sequence. And then here’s the data observable sequence.
And I’m using another type here called a driver
, which also wraps an observable sequence, but it provides some other benefits that are useful when you’re working with binding UI. Driver
s won’t error out, and they also ensure that they are on the main thread. The throttle operator helps you prevent rapid network requests, like if you’re in a type ahead searching type app like this, it’ll prevent firing off an API call every time they type in a new character. In this case I’m just throttling it by .3 seconds. Then I’m using distinctUntilChanged
again, which, as I showed you earlier, will ignore the element if it’s the same as the previous value. And then I’m using flatMapLatest
, which will switch to the latest observable sequence. And this is because when you’re receiving new search results because of additional text typed into the search field, you no longer care about the previous results sequence. flatMapLatest
will take care of that for you, and it will just switch to the latest and ignore emissions from the previous.
Then I’m calling getRepositories
in the body of the flatMapLatest
, and that takes the search text, and it’s gonna return an observable array of these repository objects that I bound to the UITableView
in the view controller.
import RxSwift
import RxCocoa
struct ViewModel {
let searchText = Variable("")
let disposeBag = DisposeBag()
lazy var data: Driver<[Repository]> = {
return self.searchText.asObservable()
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest {
self.getRepositories($0)
}
.asDriver(onErrorJustReturn: [])
}()
func getRepositories(githubId:String) -> Observable<[Repository]> {
guard let url = NSURL(string: "https://api.github.com/users/\(gitHubId)/repos")
else { return Observable.just([]) }
return NSURLSession.sharedSession()
.rx_JSON(NSURLRequest(URL: url))
.retry(3)
.map {
var data = [Repository]()
if let items = $0 as? [[String: AnyObject]] {
items.forEach {
guard let name = $0["name"] as? String,
url = $0["url"] as? String
else { return }
data.append(Repository(name: name, url: url))
}
}
return data
}
}
}
Net blocker code does a lot, so lemme walk through it.
First I create a URL that I’m gonna use in my NSURLRequest
. Then I use NSURLSession
’s sharedSession
singleton with this rxjson
extension. rxjson
takes a URL and it returns an observable sequence of the JSON data already ready to parse. And because things can go wrong with networking, and they often do, I’m going to use this retry operator, that will allow me to just retry this request three times before giving up.
And then finally, I’ll use the map operator to create an array of repository instances that return that data. It took me longer to explain that code than it did to write it.
So really, for just a few dozen lines of code, I have a handy GitHub repo app, which I kind of think is like the new Hello World app these days, demonstrating internet networking, stuff like that.
RxCoreData (26:49)
Alright, so I’ve covered some concepts, basic operators, and a couple of real world examples, using UITableView
s and doing networking. Maybe just one more. I’d like to go over a new library that provides extensions for core data.
So RxCoreData was recently introduced, and there’s definitely more work to be done, and I’m not criticizing anybody but myself, cause I’m the one that put it out there. So I need to do more work on it. And I do take pull requests if you’d like to get involved.
So we’re looking at a typical view controller implementation here, and it’s set up for working with core data, a UITableView
, and RxSwift.
So the first thing I’ll do here is use this rx_tap
extension, which is a reactive wrapper around TouchUpInside
events, essentially replacing the need to do IBAction
handlers. Then I’ll use map to create a new event instance every time the add bar button item is tapped. An event is just a struct that holds an ID and a date. Next I’ll use subscribe, and use this update extension on NSManageObject
which will update the object if it exists, or insert it if not. And finally, I’ll use the rx_entities
extension, which will either take a fetch request, or in this example, it will take a persistable type, and sort descriptors, and it will create the fetch request for you, and then it will return an observable array of this persistable type.
Then I’ll just bind that to the UITableView
using the same rx_itemsWithCellIdentifier
extension that I’ve use a couple times now.
import RxSwift
import RxCocoa
import RxDataSources
import RxCodeData
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var addBarButtonItem: UIBarButtonItem!
var managedObjectContext: NSManagedObjectContext!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bindUI()
configureTablView()
}
func nidUI() {
addBarButtonItem.rx_tap
.map { _ in
Event(id: NSUUID().UUIDString, date: NSDate())
}
.subscribeNext { [unowned self] event in
_ = try? self.managedObjectContext.update(event)
}
.addDisposableTo(disposeBag)
}
func configureTableView() {
tableView.editing = true
managedObjectContext.rx_entities(Event.self, sortDescriptors: [NSSortDescriptor(key: "date", adscending: false)])
.bindTo(tableView.rx_itemsWithCellIdentifier("Cell")) {
cell.textLabel?.text = "\(event.date)"
}
.addDisposableTo(disposeBag)
}
}
So we’re talking about just a handful of lines of code here to implement a fetched results controller–driven UITableView
. And then for deletions, I can use this rx_itemDeleted
extension, which will give me the deleted item’s index path, and then I’ll use map and rx_modelAtIndexPath
to retrieve the object and then delete it to delete it in the subscription.
//Copyright © 2016 Scott Gardner. All rights reserved.
func configureTableView() {
tableView.editing = true
managedObjectContext.rx_entities(Event.self, sortDescriptors: [NSSortDescriptor(key: "date", adscending: false)])
.bindTo(tableView.rx_itemsWithCellIdentifier("Cell")) {
cell.textLabel?.text = "\(event.date)"
}
.addDisposableTo(disposeBag)
self.tableView.rx_itemDeleted
.map { [unowned self] indexPath in
try self.tableView.tx_modelAtIndexPath(indexPath) as Event
}
.subscribeNext { [unowned self] deletedEvent in
_ = try? self.managedObjectContext.delete(dletedEvent)
}
.addDisposableTo(disposeBag)
}
That’s it.
Learn RxSwift (28:52)
So have I piqued your interest here even if you haven’t been involved with RxSwift yet? Well, cool. That’s what I hoped to do. You might be wondering at this point, how do you get started?
You already have!
That is because over this talk, I’ve gone over some really good fundamental things to know, got you off to good footing, and I’ve shown you some real world examples, which maybe piques your interest and gets you excited about what you can do with this stuff.
So the next step is really just to become more acquainted with some of the operators that are available for RxSwift. You don’t need to know them all. There’s only a handful of ones that you’ll work with in your daily travels, and you can always consult the documentation for the others. The beauty of RxSwift, though, is that as things continue to evolve with Swift, and change, the RxSwift operators will remain a relative constant.
These have been around for a long time now in all the different platforms, and they all implement the same operators, so those will be relatively the same as time goes along. Their implementation details might change, but you’ll be using them the same way. So you can learn these operators once, you can write more succinct code that does more.
There’s an interactive Rx Playground in the RxSwift project repo that will take you step by step through most of the operators, and I’ll provide a link at the end. This Playground is also categorized by type, which makes it a great ongoing reference. So then you’ll be ready to jump into the iOS, OS X or Mac OS, watchOS and tvOS platform-specific reactive extensions, and one of the best ways to get acquainted with that is to start out by going through this Rx example app, which is also part of the RxSwift repo. There are several useful examples here, and there is also examples for both iOS and OS X that you can explore.
I’ve also created an RxSwiftPlayer project, which is kind of similar to the layer player project we created a while ago to explore core animation APIs. This is actually still a work in progress, though, and I do plan to put more in there. And I’ll provide a link to that at the end, too.
So, once you’ve gained familiarity with these things, the Rx world is your oyster. There’s a growing collection of open source Rx libraries for Apple platforms in RxCommunity.
Receive news and updates from Realm straight to your inbox