Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clearing Stateful Subjects #23

Open
ahmedk92 opened this issue Aug 28, 2020 · 0 comments
Open

Clearing Stateful Subjects #23

ahmedk92 opened this issue Aug 28, 2020 · 0 comments

Comments

@ahmedk92
Copy link
Owner

ahmedk92 commented Aug 28, 2020

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)
}

Both print:

1

not:

0
1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant