2.
Observables
Written by Scott Gardner
Now that you’ve learned some of the basic concepts of RxSwift, it’s time to take the jump and play with observables.
In this chapter, you’ll go over several examples of creating and subscribing to observables. The real-world use of some of the observables may seem a bit obscure, but rest assured that you’ll acquire important skills and learn a lot about the types of observables available to you in RxSwift. You’ll use these skills throughout the rest of this book — and beyond!
Getting started
For this chapter, you’re going to use an Xcode project that’s already been set up to include a playground and the RxSwift framework. To get started, open up the Terminal app, navigate to this chapter’s starter folder and then to the RxPlayground project folder. Finally, run the bootstrap.sh
script by entering this command:
./bootstrap.sh
The bootstrap process will take a few seconds; remember that, every time you want to open this playground project, you will need to repeat the above steps. You cannot just open the playground file or workspace directly.
Select RxSwiftPlayground in the Project navigator, and you should see the following:
Twist down the playground page, through the Sources folder in the Project navigator, and select SupportCode.swift. It contains the following helper function example(of:)
:
public func example(of description: String, action: () -> Void) {
print("\n--- Example of:", description, "---")
action()
}
You’re going to use this function to encapsulate different examples as you work your way through this chapter. You’ll see how to use this function shortly.
But before you get too deep into that, now would probably be a good time to answer the question: What is an observable?
What is an observable?
Observables are the heart of Rx. You’re going to spend some time discussing what observables are, how to create them, and how to use them.
You’ll see “observable”, “observable sequence” and “sequence” used interchangeably in Rx. And, really, they’re all the same thing. You may even see an occasional “stream” thrown around from time to time, especially from developers that come to RxSwift from a different reactive programming environment. “Stream” also refers to the same thing, but, in RxSwift, all the cool kids call it a sequence, not a stream. In RxSwift…
…or something that works with a sequence. And an Observable
is just a sequence, with some special powers. One of these powers — in fact the most important one — is that it is asynchronous. Observables produce events over a period of time, which is referred to as emitting. Events can contain values, such as numbers or instances of a custom type, or they can be recognized gestures, such as taps.
One of the best ways to conceptualize this is by using marble diagrams, which are just values plotted on a timeline.
The left-to-right arrow represents time, and the numbered circles represent elements of a sequence. Element 1 will be emitted, some time will pass, and then 2 and 3 will be emitted. How much time, you ask? It could be at any point throughout the lifetime of the observable — which brings you to the next topic you’ll learn about: the lifecycle of an observable.
Lifecycle of an observable
In the previous marble diagram, the observable emitted three elements. When an observable emits an element, it does so in what’s known as a next event.
Here’s another marble diagram, this time including a vertical bar that represents the end of the road for this observable.
This observable emits three tap events, and then it ends. This is called a completed event, that is, it’s terminated. For example, perhaps the taps were on a view that was dismissed. The important thing is that the observable is terminated, and can no longer emit anything. This is normal termination. However, sometimes things can go wrong.
An error occurred in this marble diagram, represented by the red X. The observable emitted an error event containing the error. This is the same as when an observable terminates normally with a completed event. If an observable emits an error event, it is also terminated and can no longer emit anything else.
Here’s a quick recap:
- An observable emits next events that contain elements.
- It can continue to do this until a terminating event is emitted, i.e., an error or completed event.
- Once an observable is terminated, it can no longer emit events.
Events are represented as enumeration cases. Here’s the actual implementation in the RxSwift source code:
/// Represents a sequence event.
///
/// Sequence grammar:
/// **next\* (error | completed)**
public enum Event<Element> {
/// Next element is produced.
case next(Element)
/// Sequence terminated with an error.
case error(Swift.Error)
/// Sequence completed successfully.
case completed
}
Here, you see that next
events contain an instance of some Element
, error
events contain an instance of Swift.Error
and completed
events are simply stop events that don’t contain any data.
Now that you understand what an observable is and what it does, you’ll create some observables to see them in action.
Creating observables
Switch back from the current file to RxSwiftPlayground and add the code below:
example(of: "just, of, from") {
// 1
let one = 1
let two = 2
let three = 3
// 2
let observable = Observable<Int>.just(one)
}
In the above code, you:
- Define integer constants you’ll use in the following examples.
- Create an observable sequence of type
Int
using thejust
method with theone
integer constant.
The just
method is aptly named, because all it does is create an observable sequence containing just a single element. It is a static method on Observable
. However, in Rx, methods are referred to as “operators.”And the eagle-eyed among you can probably guess which operator you’re going to check out next.
Add this code into the same example:
let observable2 = Observable.of(one, two, three)
This time, you didn’t explicitly declare the type. You might think, because you give it several integers, the type is Observable
of [Int]
.
However, Option-click on observable2
to show its inferred type, and you’ll see that it’s an Observable
of Int
, not an array:
That’s because the of
operator has a variadic parameter, and Swift can infer the Observable
’s type based on it.
Pass an array to of
when you want to create an observable array. Add this code to the bottom of the example:
let observable3 = Observable.of([one, two, three])
Option-click on observable3
and you’ll see that it is indeed an Observable
of [Int]
. The just
operator can also take an array as its single element, which may seem a little weird at first. However, it’s the array that is the single element, not its contents.
Another operator you can use to create observables is from
. Add this code to the bottom of the example:
let observable4 = Observable.from([one, two, three])
The from
operator creates an observable of individual elements from an array of typed elements. Option-click on observable4
and you’ll see that it is an Observable
of Int
, not [Int]
. The from
operator only takes an array.
Your console is probably looking quite bare at the moment. That’s because you haven’t printed anything except the example header. Time to change that by subscribing to observables.
Subscribing to observables
From your experience as an iOS developer, you are likely familiar with NotificationCenter
; it broadcasts notifications to observers. However, these observed notifications are different than RxSwift Observable
s. Here’s an example of an observer of the UIKeyboardDidChangeFrame
notification, with a handler as a trailing closure (don’t add this code to your playground):
let observer = NotificationCenter.default.addObserver(
forName: UIResponder.keyboardDidChangeFrameNotification,
object: nil,
queue: nil) { notification in
// Handle receiving notification
}
Subscribing to an RxSwift observable is fairly similar; you call observing an observable subscribing to it. So instead of addObserver()
, you use subscribe()
. Unlike NotificationCenter
, where developers typically use only its .default
singleton instance, each observable in Rx is different.
More importantly, an observable won’t send events, or perform any work, until it has a subscriber.
Remember, an observable is really a sequence definition, and subscribing to an observable is really more like calling next()
on an Iterator
in the Swift standard library (don’t add this code to your playground):
let sequence = 0..<3
var iterator = sequence.makeIterator()
while let n = iterator.next() {
print(n)
}
/* Prints:
0
1
2
*/
Subscribing to observables is more streamlined. You can also add handlers for each event type an observable can emit. Recall that an observable emits next
, error
and completed
events. A next
event passes the element being emitted to the handler, and an error
event contains an error instance.
To see this in action, add this new example to your playground. Remember to insert each new example on its own, after the closing curly bracket of the previous example.
example(of: "subscribe") {
let one = 1
let two = 2
let three = 3
let observable = Observable.of(one, two, three)
}
This is similar to the previous example, except this time you’re using the of
operator. Now add this code at the bottom of this example, to subscribe to the observable:
observable.subscribe { event in
print(event)
}
Note: The Console should automatically appear whenever there is output, but you can manually show it by selecting View ▸ Debug Area ▸ Activate Console from the menu. This is where the
Option-click on the subscribe
operator, and observe it takes a closure parameter that receives an Event
of type Int
and doesn’t return anything, and subscribe
returns a Disposable
. You’ll learn about disposables shortly.
This subscription will print out each event emitted by observable
:
--- Example of: subscribe ---
next(1)
next(2)
next(3)
completed
The observable emits a next
event for each element, then emits a completed
event and is terminated. When working with observables, you’ll usually be primarily interested in the elements emitted by next
events, rather than the events themselves.
To see one way to access the elements directly, replace the subscribing code from above with the following code:
observable.subscribe { event in
if let element = event.element {
print(element)
}
}
Event
has an element
property. It’s an optional value, because only next
events have an element. So you use optional binding to unwrap the element if it’s not nil
. Now only the elements are printed, not the events containing the elements, nor the completed
event:
1
2
3
That’s a nice pattern, and it’s so frequently used that there’s a shortcut for it in RxSwift. There’s a subscribe
operator for each type of event an observable emits: next, error and completed.
Replace the previous subscription code with this:
observable.subscribe(onNext: { element in
print(element)
})
Note: If you have code completion suggestions turned on in Xcode preferences, you may be asked for handlers for the other events. Ignore these for now.
Now you’re only handling next
event elements and ignoring everything else. The onNext
closure receives the next
event’s element as an argument, so you don’t have to manually extract it from the event like you did before.
Now you know how to create observable of one element and of many elements. But what about an observable of zero elements? The empty
operator creates an empty observable sequence with zero elements; it will only emit a completed
event.
Add this new example to your playground:
example(of: "empty") {
let observable = Observable<Void>.empty()
}
An observable must be defined as a specific type if it cannot be inferred. So the type must be explicitly defined, because empty
has nothing from which to infer the type. Void
is typically used because nothing is going to be emitted.
Add this code to the example to subscribe to the empty observable:
observable.subscribe(
// 1
onNext: { element in
print(element)
},
// 2
onCompleted: {
print("Completed")
}
)
In the above code, you:
- Handle
next
events, just like you did in the previous example. - Simply print a message, because a
.completed
event does not include an element.
In the console, you’ll see that empty
only emits a .completed
event:
--- Example of: empty ---
Completed
What use is an empty observable? They’re handy when you want to return an observable that immediately terminates or intentionally has zero values.
As opposed to the empty
operator, the never
operator creates an observable that doesn’t emit anything and never terminates. It can be use to represent an infinite duration. Add this example to your playground:
example(of: "never") {
let observable = Observable<Void>.never()
observable.subscribe(
onNext: { element in
print(element)
},
onCompleted: {
print("Completed")
}
)
}
Nothing is printed, except for the example header. Not even "Completed"
. How do you know if this is even working? Hang on to that inquisitive spirit until the Challenges section.
So far, you’ve worked with observables of specific elements or values. However, it’s also possible to generate an observable from a range of values.
Add this example to your playground:
example(of: "range") {
// 1
let observable = Observable<Int>.range(start: 1, count: 10)
observable
.subscribe(onNext: { i in
// 2
let n = Double(i)
let fibonacci = Int(
((pow(1.61803, n) - pow(0.61803, n)) /
2.23606).rounded()
)
print(fibonacci)
})
}
What you just did:
- Create an observable using the
range
operator, which takes astart
integer value and acount
of sequential integers to generate. - Calculate and print the nth Fibonacci number for each emitted element.
There’s actually a better place than in the onNext
handler to put code that transforms the emitted element. You’ll learn about that in Chapter 7, “Transforming Operators.”
Except for the never()
example, up to this point you’ve worked with observables that automatically emit a completed
event and naturally terminate.
Doing so allowed you to focus on the mechanics of creating and subscribing to observables, but this brushed an important aspect of subscribing to observables under the carpet. It’s time to do some housekeeping before moving on.
Disposing and terminating
Remember that an observable doesn’t do anything until it receives a subscription. It’s the subscription that triggers an observable’s work, causing it to emit new events until an error
or completed
event terminates the observable. However, you can also manually cause an observable to terminate by canceling a subscription to it.
Add this new example to your playground:
example(of: "dispose") {
// 1
let observable = Observable.of("A", "B", "C")
// 2
let subscription = observable.subscribe { event in
// 3
print(event)
}
}
Quite simply:
- Create an observable of strings.
- Subscribe to the observable, this time saving the returned
Disposable
as a local constant calledsubscription
. - Print each emitted
event
in the handler.
To explicitly cancel a subscription, call dispose()
on it. After you cancel the subscription, or dispose of it, the observable in the current example will stop emitting events.
Add this code to the bottom of the example:
subscription.dispose()
Managing each subscription individually would be tedious, so RxSwift includes a DisposeBag
type. A dispose bag holds disposables — typically added using the disposed(by:)
method — and will call dispose()
on each one when the dispose bag is about to be deallocated.
Add this new example to your playground:
example(of: "DisposeBag") {
// 1
let disposeBag = DisposeBag()
// 2
Observable.of("A", "B", "C")
.subscribe { // 3
print($0)
}
.disposed(by: disposeBag) // 4
}
Step-by-step, you:
- Create a dispose bag.
- Create an observable.
- Subscribe to the observable and print out the emitted events using the default argument name
$0
. - Add the returned
Disposable
fromsubscribe
to the dispose bag.
This is the pattern you’ll use most frequently: creating and subscribing to an observable, and immediately adding the subscription to a dispose bag.
Why bother with disposables at all?
If you forget to add a subscription to a dispose bag, or manually call dispose
on it when you’re done with the subscription, or in some other way cause the observable to terminate at some point, you will probably leak memory.
Don’t worry if you forget; the Swift compiler should warn you about unused disposables.
In the previous examples, you created observables with specific next
event elements. Using the create
operator is another way to specify all the events an observable will emit to subscribers.
Add this new example to your playground:
example(of: "create") {
let disposeBag = DisposeBag()
Observable<String>.create { observer in
}
}
The create
operator takes a single parameter named subscribe
. Its job is to provide the implementation of calling subscribe
on the observable. In other words, it defines all the events that will be emitted to subscribers.
If you option-click on create
right now you will not get the Quick Help documentation, because this code won’t compile yet. So here’s a preview:
The subscribe
parameter is an escaping closure that takes an AnyObserver
and returns a Disposable
. AnyObserver
is a generic type that facilitates adding values onto an observable sequence, which will then be emitted to subscribers.
Change the implementation of create
to the following:
Observable<String>.create { observer in
// 1
observer.onNext("1")
// 2
observer.onCompleted()
// 3
observer.onNext("?")
// 4
return Disposables.create()
}
Here’s what you do with this code:
- Add a
next
event onto the observer.onNext(_:)
is a convenience method foron(.next(_:))
. - Add a
completed
event onto the observer. Similarly,onCompleted
is a convenience method foron(.completed)
. - Add another
next
event onto the observer. - Return a disposable, defining what happens when your observable is terminated or disposed of; in this case, no cleanup is needed so you return an empty disposable.
Note: The last step, returning a
Disposable
, may seem strange at first. Remember thatsubscribe
operators must return a disposable representing the subscription, so you useDisposables.create()
to create a disposable.
Do you think the second onNext
element (?
) could ever be emitted to subscribers? Why or why not?
To see if you guessed correctly, subscribe to the observable by adding the following code on the next line after the create
implementation:
.subscribe(
onNext: { print($0) },
onError: { print($0) },
onCompleted: { print("Completed") },
onDisposed: { print("Disposed") }
)
.disposed(by: disposeBag)
You subscribed to the observable, and implemented all the handlers using default argument names for element and error arguments passed to the onNext
and onError
handlers, respectively. The result is, the first next
event element, "Completed"
and "Disposed"
are printed out. The second next
event is not printed because the observable emitted a completed
event and terminated before it is added.
--- Example of: create ---
1
Completed
Disposed
What would happen if you add an error to the observer
? Add this code at the top of the example to define an error type with a single case:
enum MyError: Error {
case anError
}
Next, add the following line of code between the observer.onNext
and observer.onCompleted
calls:
observer.onError(MyError.anError)
Now the observable emits the error and then terminates:
--- Example of: create ---
1
anError
Disposed
What would happen if you did not add a completed
or error
event, and also didn’t add the subscription to disposeBag
? Comment out the observer.onError
, observer.onCompleted
and disposed(by: disposeBag)
lines of code to find out.
Here’s the complete implementation:
example(of: "create") {
enum MyError: Error {
case anError
}
let disposeBag = DisposeBag()
Observable<String>.create { observer in
// 1
observer.onNext("1")
// observer.onError(MyError.anError)
// 2
// observer.onCompleted()
// 3
observer.onNext("?")
// 4
return Disposables.create()
}
.subscribe(
onNext: { print($0) },
onError: { print($0) },
onCompleted: { print("Completed") },
onDisposed: { print("Disposed") }
)
// .disposed(by: disposeBag)
}
Congratulations, you’ve just leaked memory! The observable will never finish, and the disposable will never be disposed.
--- Example of: create ---
1
?
Feel free to uncomment the line that adds the completed
event or the code that adds the subscription to the disposeBag
if you just can’t stand to leave this example in a leaky state.
Creating observable factories
Rather than creating an observable that waits around for subscribers, it’s possible to create observable factories that vend a new observable to each subscriber.
Add this new example to your playground:
example(of: "deferred") {
let disposeBag = DisposeBag()
// 1
var flip = false
// 2
let factory: Observable<Int> = Observable.deferred {
// 3
flip.toggle()
// 4
if flip {
return Observable.of(1, 2, 3)
} else {
return Observable.of(4, 5, 6)
}
}
}
From the top, you:
- Create a
Bool
flag to flip which observable to return. - Create an observable of
Int
factory using thedeferred
operator. - Toggle
flip
, which happens each timefactory
is subscribed to. - Return different observables based on whether
flip
istrue
orfalse
.
Externally, an observable factory is indistinguishable from a regular observable. Add this code to the bottom of the example to subscribe to factory
four times:
for _ in 0...3 {
factory.subscribe(onNext: {
print($0, terminator: "")
})
.disposed(by: disposeBag)
print()
}
Each time you subscribe to factory
, you get the opposite observable. In other words, you get 123
, then 456
, and the pattern repeats each time a new subscription is created:
--- Example of: deferred ---
123
456
123
456
Using Traits
Traits are observables with a narrower set of behaviors than regular observables. Their use is optional; you can use a regular observable anywhere you might use a trait instead. Their purpose is to provide a way to more clearly convey your intent to readers of your code or consumers of your API. The context implied by using a trait can help make your code more intuitive.
There are three kinds of traits in RxSwift: Single
, Maybe
and Completable
. Without knowing anything more about them yet, can you guess how each one is specialized?
Single
s will emit either a success(value)
or error(error)
event. success(value)
is actually a combination of the next
and completed
events. This is useful for one-time processes that will either succeed and yield a value or fail, such as when downloading data or loading it from disk.
A Completable
will only emit a completed
or error(error)
event. It will not emit any values. You could use a completable when you only care that an operation completed successfully or failed, such as a file write.
Finally, Maybe
is a mashup of a Single
and Completable
. It can either emit a success(value)
, completed
or error(error)
. If you need to implement an operation that could either succeed or fail, and optionally return a value on success, then Maybe
is your ticket.
You’ll have an opportunity to work more with traits in Chapter 4, “Observables & Subjects in Practice,” and beyond. For now, you’ll run through a basic example of using a single to load some text from a text file named Copyright.txt in the Resources folder for this playground — because who doesn’t love some legalese once in a while?
Add this example to your playground:
example(of: "Single") {
// 1
let disposeBag = DisposeBag()
// 2
enum FileReadError: Error {
case fileNotFound, unreadable, encodingFailed
}
// 3
func loadText(from name: String) -> Single<String> {
// 4
return Single.create { single in
}
}
}
In the above code, you:
- Create a dispose bag to use later.
- Define an
Error
enum to model some possible errors that can occur in reading data from a file on disk. - Implement a function to load text from a file on disk that returns a
Single
. - Create and return a
Single
.
Add this code inside the create
closure to complete the implementation:
// 1
let disposable = Disposables.create()
// 2
guard let path = Bundle.main.path(forResource: name, ofType: "txt") else {
single(.error(FileReadError.fileNotFound))
return disposable
}
// 3
guard let data = FileManager.default.contents(atPath: path) else {
single(.error(FileReadError.unreadable))
return disposable
}
// 4
guard let contents = String(data: data, encoding: .utf8) else {
single(.error(FileReadError.encodingFailed))
return disposable
}
// 5
single(.success(contents))
return disposable
With this code, you:
- Create a
Disposable
, because thesubscribe
closure ofcreate
expects it as its return type. - Get the path for the filename, or else add a file not found error onto the
Single
and return the disposable you created. - Get the data from the file at that path, or add an unreadable error onto the
Single
and return the disposable. - Convert the data to a string; otherwise, add an encoding failed error onto the
Single
and return the disposable. Starting to see a pattern here? - Made it this far? Add the contents onto the
Single
as a success, and return the disposable.
Now you can put this function to work. Add this code to the example:
// 1
loadText(from: "Copyright")
// 2
.subscribe {
// 3
switch $0 {
case .success(let string):
print(string)
case .error(let error):
print(error)
}
}
.disposed(by: disposeBag)
Here, you:
- Call
loadText(from:)
and pass the root name of the text file. - Subscribe to the
Single
it returns. - Switch on the event and print the string if it was successful, or print the error if not.
You should see the text from the file printed to the console, which is the same as the copyright comment at the bottom of the playground:
--- Example of: Single ---
Copyright (c) 2020 Razeware LLC
...
Try changing the filename to something else, and you should see the file not found error printed instead.
Challenges
Practice makes permanent. By completing these challenges, you’ll practice what you’ve learned in this chapter and pick up a few more tidbits of knowledge about working with observables.
A starter playground workspace as well as the finished version of it are provided for each challenge. Enjoy!
Challenge 1: Perform side effects
In the never
operator example earlier, nothing printed out. That was before you added your subscriptions to dispose bags. However, if you had added a subscription to a dispose bag, you also could’ve printed out a message in subscribe
’s onDisposed
handler.
There is another useful operator for when you want to do some side work that doesn’t affect the observable’s emitted events.
The do
operator enables you to insert side effects; that is, handlers to do things that will not change the emitted event in any way. Instead, do
will just pass the event through to the next operator in the chain. Unlike subscribe
, do
also includes an onSubscribe
handler.
The method for using the do
operator is do(onNext:onError:onCompleted:onSubscribe:onDispose)
. Each parameter is optional and defaults to nil
, so you can provide handlers for just the events you’re interested in. Use Xcode’s autocompletion to get the closure parameters for each of the events.
To complete this challenge, insert the do
operator in the never
example using the onSubscribe
handler. Feel free to include any of the other handlers; they work just like subscribe
’s handlers do.
And while you’re at it, create a dispose bag and add the subscription to it.
Don’t forget you can always peek at the finished challenge playground for “inspiration.”
Challenge 2: Print debug info
Performing side effects is one way to debug your Rx code. But it turns out that there’s even a better utility for that purpose: the debug
operator, which will print information about every event for an observable.
It has several optional parameters, but perhaps the most useful one let’s you include an identifier string that is also printed out. In complex Rx chains, where you might add debug
calls in multiple places, this can really help differentiate the source of each printout.
Continuing to work in the playground from the previous challenge, complete this challenge by replacing the use of the do
operator with debug
and provide a string identifier to it as a parameter. Observe the debug output in Xcode’s console.