Subjects
import UIKit
import RxSwift
func example(of description: String, action: () -> Void) {
print("\n--- Example of:", description, "---")
action()
}
Observables are a fundamental part of RxSwift, but they’re essentially read-only. You may only subscribe to them to get notified of new events they produce.
A common need when developing apps is to manually add new values onto an observable during runtime to emit to subscribers. What you want is something that can act as both an observable and as an observer. That something is called a Subject.
like a newspaper publisher, it will receive information and then publish it to subscribers. It’s of type String, so it can only receive and publish strings. After being initialized, it’s ready to receive strings.
example(of: "PublishSubject") {
let subject = PublishSubject<String>()
subject.on(.next("Is anyone listening?"))
let subscriptionOne = subject
.subscribe(onNext: { string in
print(string)
})
// PublishSubject only emits to current subscribers.
// So if you weren’t subscribed to it when an event was added to it,
// you won’t get it when you do subscribe.
// Now, because subject has a subscriber, it will emit the added value:
// shortcut for .on(.next("")) -> .onNext("")
subject.onNext("1")
subject.onNext("2")
}
MARK: - What are subjects?
Subjects act as both an observable and an observer.
You saw earlier how they can receive events and also be subscribed to.
There are four subject types in RxSwift
PublishSubject: :Starts empty and only emits new elements to subscribers.
BehaviorSubject: Starts with an initial value and replays it or the latest element to new subscribers.
ReplaySubject: Initialized with a buffer size and will maintain a buffer of elements up to that size and replay it to new subscribers.
AsyncSubject: Emits only the last 'next' event in the sequence, and only when the subject receives a 'completed' event.
This is a seldom used kind of subject, and you won't use it in this book.
RxSwift also provides a concept called Relays. RxSwift provides two of these, named PublishRelay and BehaviorRelay. These wrap their respective subjects, but only accept and relay next events. You cannot add a completed or error event onto relays at all, so they’re great for non-terminating sequences.
MARK: - Working with Publish Subjects
Publish subjects come in handy when you simply want subscribers to be notified of new events from the point at which they subscribed,
until either they unsubscribe, or the subject has terminated with a completed or error event.
The top line is the publish subject and the second and third lines are subscribers.
The upward-pointing arrows indicate subscriptions, and the downward pointing arrows represent emitted events.
The first subscriber subscribes after '1' is added to the subject, so it doesn't receive that event. It does get '2' and '3', though. And because the second subscriber doesn’t join in until after 2 is added, it only gets 3.
Behavior subject
Behavior subjects work similarly to publish subjects, except they will replay the latest 'next' event to new subscribers.
The first line at the top is the subject. The first subscriber on the second line down subscribes after '1' but before '2', so it receives '1' immediately upon subscription, and then '2' and '3' when they're emitted by the subject.
Similarly, the second subscriber subscribes after '2' but before '3', so it receives '2' immediately and then '3' when it's emitted.
Replay subject
A variant of Subject that 'replays' or emits old values to new subscribers. It buffers a set number of values and will emit those values immediately to any new subscribers in addition to emitting new values to existing subscribers.
The following marble diagram depicts a replay subject with a buffer size of '2'.
The first subscriber (middle line) is already subscribed to the replay subject (top line) so it gets elements as they’re emitted. The second subscriber (bottom line) subscribes after 2, so it gets 1 and 2 replayed to it.
copied from playground
import UIKit
import RxSwift
import RxCocoa
func example(of description: String, isOn: Bool = false, action: () -> Void) {
if isOn {
print("\n--- Example of:", description, "---")
action()
}
}
//Observables are a fundamental part of RxSwift, but they’re essentially read-only. You may only subscribe to them to get notified of new events they produce.
//A common need when developing apps is to manually add new values onto an observable during runtime to emit to subscribers. What you want is something that can act as both an observable and as an observer. That something is called a Subject.
//like a newspaper publisher, it will receive information and then publish it to subscribers. It’s of type String, so it can only receive and publish strings. After being initialized, it’s ready to receive strings.
example(of: "PublishSubject") {
let subject = PublishSubject<String>()
subject.on(.next("Is anyone listening?"))
let subscriptionOne = subject
.subscribe(onNext: { string in
print(string)
})
// PublishSubject only emits to current subscribers.
// So if you weren’t subscribed to it when an event was added to it, you won’t get it when you do subscribe.
// Now, because subject has a subscriber, it will emit the added value:
// shortcut for .on(.next("")) -> .onNext("")
subject.onNext("1")
subject.onNext("2")
let subscriptionTwo = subject
.subscribe { event in
print("2)", event.element ?? event)
}
subject.onNext("3")
subscriptionOne.dispose()
subject.onNext("4")
// When a publish subject receives a completed or error event, also known as a stop event, it will emit that stop event to new subscribers and it will no longer emit next events.
// However, it will re-emit its stop event to future subscribers.
// Terminates the subject's observable sequence.
subject.onCompleted()
subject.onNext("5")
subscriptionTwo.dispose()
let disposeBag = DisposeBag()
subject.subscribe {
print("3)", $0.element ?? $0)
}
.disposed(by: disposeBag)
subject.onNext("?")
// Publish subjects don’t replay values to new subscribers.
// This makes them a good choice to model events such as “user tapped something” or “notification just arrived.”
}
// MARK: - What are subjects?
// Subjects act as both an observable and an observer.
// You saw earlier how they can receive events and also be subscribed to.
// There are four subject types in RxSwift
// PublishSubject: :Starts empty and only emits new elements to subscribers.
// BehaviorSubject: Starts with an initial value and replays it or the latest element to new subscribers.
// ReplaySubject: Initialized with a buffer size and will maintain a buffer of elements up to that size and replay it to new subscribers.
// AsyncSubject: Emits only the last 'next' event in the sequence, and only when the subject receives a 'completed' event.
// This is a seldom used kind of subject, and you won't use it in this book.
// RxSwift also provides a concept called Relays. RxSwift provides two of these, named PublishRelay and BehaviorRelay.
//These wrap their respective subjects, but only accept and relay next events.
//You cannot add a completed or error event onto relays at all, so they’re great for non-terminating sequences.
// MARK: - Working with Publish Subjects
// Publish subjects come in handy when you simply want subscribers to be notified of new events from the point at which they subscribed,
// until either they unsubscribe, or the subject has terminated with a completed or error event.
// MARK: - Working with behavior subjects
// Behavior subjects work similarly to publish subjects, except they will replay the latest 'next' event to new subscribers.
enum MyError: Error {
case anError
}
func print<T: CustomStringConvertible>(label: String, event: Event<T>) {
print(label, (event.element ?? event.error) ?? event)
}
example(of: "BehaviorSubject") {
// Because BehaviorSubject always emits its latest element, you can't create one without providing an initial value.
// If you can't provide an initial value at creation time,
// that probably means you need to use a PublishSubject instead, or model your element as an Optional.
let subject = BehaviorSubject(value: "Initial value")
let disposeBag = DisposeBag()
subject.onNext("X")
subject
.subscribe {
print(label: "1)", event: $0)
}
.disposed(by: disposeBag)
// You subscribe to the subject immediately after it was created.
// Because no other elements have been added to the subject, it replays its initial value to the subscriber.
subject.onError(MyError.anError)
subject
.subscribe {
print(label: "2)", event: $0)
}
.disposed(by: disposeBag)
}
// Behavior subjects are useful when you want to pre-populate a view with the most recent data. For example, you could bind controls in a user profile screen to a behavior subject, so that the latest values can be used to pre-populate the display while the app fetches fresh data.
//What if you wanted to show more than the latest value? For example, on a search screen, you may want to show the most recent five search terms used. This is where replay subjects come in.
// MARK: - Working with replay subjects
// Replay subjects will temporarly cache, or buffer, the latest elements they emit, up to a specified size of your choosing.
// They will then replay that buffer to new subscribers.
// buffer: 어떤 장치에서 다른 장치로 데이터를 송신할 때 일어나는 시간의 차이나 데이터 흐름의 속도 차이를 조정하기 위해 일시적으로 데이터를 기억시키는 장치.
// When using a replay subject, this buffer is held in memory. You can definitely shoot yourself in the foot here, such as if you set a large buffer size for a replay subject of some type whose instances each take up a lot of memory, like images.
//Another thing to watch out for is creating a replay subject of an array of items. Each emitted element will be an array, so the buffer size will buffer that many arrays. It would be easy to create memory pressure here if you’re not careful.
example(of: "ReplaySubject") {
let subject = ReplaySubject<String>.create(bufferSize: 2)
let disposeBag = DisposeBag()
subject.onNext("1")
subject.onNext("2")
subject.onNext("3")
subject
.subscribe {
print(label: "1)", event: $0)
}
.disposed(by: disposeBag)
subject
.subscribe {
print(label: "2)", event: $0)
}
.disposed(by: disposeBag)
subject.onNext("4")
subject.onError(MyError.anError)
// Explicitly calling dispose() on a replay subject like this isn’t something you generally need to do
subject.dispose()
// If you’ve added your subscriptions to a dispose bag, then everything will be disposed of and deallocated when the owner — such as a view controller or view model — is deallocated.
subject
.subscribe {
print(label: "3)", event: $0)
}
.disposed(by: disposeBag)
}
//Note: In case you’re wondering what is a ReplayMany, it’s an internal type that is used to create replay subjects.
// MARK: - Working with relays
// Relay wraps a subject while maintaining its replay behavior.
// Unlike other subjects - and observables in general - you add a value onto a relay by using the 'accept(_:) method.
// In other words, you don't use 'onNext(_:)'.
// This is because relays can only accept values, i.e., you cannot add an error or completed event onto them.
// A PublishRelay wraps a PublishSubject
// and a BehaviorRelay wraps a BehaviorSubject.
// What sets relays apart from their wrapped subjects is that
// they are guaranteed to never terminate.
// There is no way to add an error or completed event onto a relay.
// Any attempt to do so such as the following will generate a compiler error
// relay.accept(MyError.anError)
// relay.onCompleted()
// Remember that publish relays wrap a publish subject and work just like them,
// except the accept part and that they will not terminate.
example(of: "PublishRelay", isOn: false) {
// The output is the same as if you'd created a publish subject instead of a relay:
let relay = PublishRelay<String>()
let disposeBag = DisposeBag()
relay.accept("Knock knock, anyone home?")
relay
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
relay.accept("1")
}
example(of: "BehaviorRelay", isOn: true) {
let relay = BehaviorRelay(value: "Initial value")
let disposeBag = DisposeBag()
relay.accept("New initial value")
relay.subscribe {
print(label: "1)", event: $0)
}
.disposed(by: disposeBag)
relay.accept("1")
relay
.subscribe {
print(label: "2)", event: $0)
}
.disposed(by: disposeBag)
relay.accept("2")
// Behavior relays let you directly access their current value.
// In this case, the latest value added onto the relay is '2',
// so that's what is printed to the console.
print(relay.value)
}
//Behavior relays are versatile. You can subscribe to them to be able to react whenever a new next event is emitted, just like any other subject. And they can accommodate one-off needs, such as when you just need to check the current value without subscribing to receive updates.