Functional reactive programming (FRP) frameworks have a class of subjects that are stateful. That is, they store a value that can be queried, or sent immediately to new subscribers upon subscription. Combine's CurrentValueSubject and RxSwift's BehaviorSubject are good examples.
Sometimes we need to prevent new subscribers from immediately receiving the stored value and instead expect a new value. This is what can be called clearing or resetting the subject. Looking up this problem on the internet yields a solution that involves both the producer and the consumer agreeing on a special value that can be ignored. Luckily, Swift's optional can be leveraged to make this look more brief and tidy.
Usually, to prevent external mutation, subjects are an implementation detail to an exposed publisher/observable (in Combine/RxSwift terminologies). So, we can define our subject to be of Optional<T> where T is our output value type. Then, we use compactMap on the subject to expose the publisher/observable. This way we can send nil values to the subject to replace the current value while being filtered out by compactMap.
Combine Example:
import UIKit
import Combine
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
intProvider.experiment()
cancellable = intProvider.publisher.sink { int in
print(int)
}
}
private let intProvider = IntProvider()
private var cancellable: AnyCancellable?
}
class IntProvider {
var publisher: AnyPublisher<Int, Never> {
subject
.compactMap({ $0 })
.eraseToAnyPublisher()
}
func experiment() {
subject.send(nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.subject.send(1)
}
}
private let subject = CurrentValueSubject<Int?, Never>(0)
}
RxSwift Example:
import UIKit
import RxSwift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
intProvider.experiment()
disposable = intProvider.int.subscribe(onNext: { int in
print(int)
})
}
private let intProvider = IntProvider()
private var disposable: Disposable?
}
class IntProvider {
var int: Observable<Int> {
subject.compactMap({ $0 })
}
func experiment() {
subject.onNext(nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.subject.onNext(1)
}
}
private let subject = BehaviorSubject<Int?>(value: 0)
}
Functional reactive programming (FRP) frameworks have a class of subjects that are stateful. That is, they store a value that can be queried, or sent immediately to new subscribers upon subscription. Combine's
CurrentValueSubject
and RxSwift'sBehaviorSubject
are good examples.Sometimes we need to prevent new subscribers from immediately receiving the stored value and instead expect a new value. This is what can be called clearing or resetting the subject. Looking up this problem on the internet yields a solution that involves both the producer and the consumer agreeing on a special value that can be ignored. Luckily, Swift's optional can be leveraged to make this look more brief and tidy.
Usually, to prevent external mutation, subjects are an implementation detail to an exposed publisher/observable (in Combine/RxSwift terminologies). So, we can define our subject to be of
Optional<T>
whereT
is our output value type. Then, we usecompactMap
on the subject to expose the publisher/observable. This way we can sendnil
values to the subject to replace the current value while being filtered out bycompactMap
.Combine Example:
RxSwift Example:
Both print:
not: