How to use a Publish subject to observe a variable's value? - swift

I'm new to using RxSwift framework, I'm actually learning and trying to understand the basics and I would like some help from you please.
private func observeCurrentIndex() -> Observable<Int> {
return Observable<Int>.create { (observer) -> Disposable in
observer.onNext(self.currentIndex)
return Disposables.create()
}
}
Here I've created an observable on currentIndex which is an int. When I subscribe to it, I get only the first value of currentIndex which is 2. Is it not supposed to notify me whenever currentIndex changes(just like a KVO would)?
override func viewDidLoad() {
super.viewDidLoad()
observeCurrentIndex()
.subscribe(onNext: { (valeur) in
print("new value \(valeur)")
})
.addDisposableTo(disposeBag)
}
To be notified each time currentIndex changes value, I've been told that I have to use a publishSubject for that.
#IBAction func increaseAction(_ sender: UIButton) {
if currentIndex <= kMaxIndex {
currentIndex = currentIndex + 1
}
}
Could someone indicate to me where and how to do this? Thanks in advance.

Usually, Subjects are used to bridge an imperative API to reactive world. More information on how to use subject can be found here.
There are a couple solution to observe a variable evolution using RxSwift's primitives
Using KVO
class WithIndex: NSObject {
dynamic var currentIndex: Int
func observeCurrentIndex() -> Observable<Int> {
return instance.rx.observe(Int.self, "currentIndex")
}
#IBAction func increaseAction(_ sender: UIButton) {
if currentIndex <= kMaxIndex {
currentIndex = currentIndex + 1
}
}
}
The drawback with this solution is that WithIndex needs to inherit from NSObject for KVO to be available.
Using Variable
class WithIndex {
let currentIndex: Variable<Int>
func observeCurrentIndex() -> Observable<Int> {
return currentIndex.asObservable()
}
#IBAction func increaseAction(_ sender: UIButton) {
if currentIndex.value <= kMaxIndex {
currentIndex.value = currentIndex.value + 1
}
}
}
This one is more pratical. You can then set currentIndex's value using currentIndex.value = 12 and observe using currentIndex.asObservable().subscribe(...).
Variable is a simple wrapper around BehaviorSubject and will send a .next event every time variable's value changes.

I come from RxJS, but I think what you really need is a ReplaySubject.
The first code you provided, you're just creating an observable that only returns 1 value. The code inside Observable.create just gets executed once every time someone .subscribe()s on it.
A publish is more meant to share a subscription between many subscribers... The perfect case for that is that if you need a specific info gathered from one server to be used in many places in your app... You don't want to spam the server with so many requests, so you just make one request, publish that and observers will subscribe to that published stream.
A ReplaySubject (or BehaviourSubject, but then you need to know the initial value when you initialize it) is more in the line of what you want: It's both an Observable and an Observer, an object where other observers can subscribe to, and every time you call .onNext() to it, all subscribers will get a new value.
Rx can't do magic, it doesn't know when/how you are editing your variables. So you will need to create a ReplaySubject of length 1 in your object, and return that to subscribers. Then track on your code whenever your currentIndex changes, call the onNext method on that ReplaySubject.

Related

How to make a proper reactive extension on Eureka SelectableSection

This is my first question to the StackOverflow community so excuse me if I'm doing something wrong.
1. What I'm trying to achieve
Basically, I want to make a custom reactive wrapper around Eureka's SelectableSection class in order to observe the value of the selected row when it is changed. I'm thinking to get this data from the onSelectSelectableRow closure which is called every time a row is selected.
2. What I've tried to do for that
Actually, I've got this working but it's not a generic use of the custom wrapper, here is the example that works but only when I specify the row and its value type, for example ListCheckRow<Int>.
extension SelectableSection: ReactiveCompatible {}
extension Reactive where Base : SelectableSection<ListCheckRow<Int>> {
var selectedValue: Observable<Base.SelectableRow.Cell.Value?> {
return Observable.create { observer in
self.base.onSelectSelectableRow = {cell, row in
observer.onNext(row.value)
}
return Disposables.create {
observer.onCompleted()
}
}
}
}
This works fine and as I expected but when it comes to something more generic like the next code example, I get an error saying that: "Cannot assign to property: 'base' is a 'let' constant"
extension SelectableSection: ReactiveCompatible {}
extension Reactive where Base : SelectableSectionType {
var selectedValue: Observable<Base.SelectableRow.Cell.Value?> {
return Observable.create { observer in
self.base.onSelectSelectableRow = {cell, row in // Error: Cannot assign to property: 'base' is a 'let' constant
observer.onNext(row.value)
}
return Disposables.create {
observer.onCompleted()
}
}
}
}
Any help will be much appreciated, thanks. šŸ™
The fundamental problem here is that SelectableSectionType is a protocol that isn't restricted to class types and Reactive assumes that Base is a class (or otherwise is not going to be modified by the observable creation.)
I think the most generic you can make this is something like:
extension Reactive {
func selectedValue<Row, T>() -> Observable<T?> where Base: SelectableSection<Row>, Row: SelectableRowType, T == Row.Cell.Value {
Observable.create { [base] observer in
base.onSelectSelectableRow = { cell, row in
observer.onNext(row.value) // this is problematic. See below.
}
return Disposables.create {
observer.onCompleted() // this is wrong. See below.
}
}
}
}
The biggest problem with the above though is that if you subscribe to the resulting Observable more than once or create more than one Observable using this computed property, all but the last subscription will silently fail. The simple way to fix this is to always remember to share any result but that's rather error prone.
The way to fix this would be to associate a Subject with each SelectableSection, but you can't modify the class, so what are we to do?
Here's a solution:
extension Reactive {
func selectedValue<Row, T>() -> Observable<T?> where Base: SelectableSection<Row>, Row: SelectableRowType, T == Row.Cell.Value {
Observable.create { [base] observer in
if let block = selectableSections.first(where: { $0.section === base }) {
let subject = block.subject as! PublishSubject<T?>
return Disposables.create(
block.disposable.retain(),
subject.subscribe(observer)
)
}
else {
let subject = PublishSubject<T?>()
let block = SelectableSectionBlock(
section: base,
subject: subject,
disposable: RefCountDisposable(disposable: Disposables.create {
selectableSections.removeAll(where: { $0.section === base })
})
)
base.onSelectSelectableRow = { cell, row in
subject.onNext(row.value)
}
selectableSections.append(block)
return Disposables.create(
block.disposable,
subject.subscribe(observer)
)
}
}
}
}
private struct SelectableSectionBlock {
let section: Section
let subject: Any
let disposable: RefCountDisposable
}
private var selectableSections = [SelectableSectionBlock]()
The selectableSections array stores a Subject and RefCountDisposable for each SelectableSection.
Whenever an Observable is created, or subscribed to...
if it's the first time working with this section, it will create a Subject and RefCountDisposable assign the onSelectSelectableRow to send a next event to the subject and store the subject in the array.
otherwise it will find the subject and disposable associated with this Section and retain the disposable.
Once it has the subject and disposable from above, it will subscribe the new observer to the subject and return a new Disposable that will remove that subscription and decrement the ref-count when the time comes.
Yes this is quite a bit more complex than the simple assignment case, but it's the right thing to do.
As for calling onCompleted() inside the disposable closure. By the time the closure is called, the observer has already emitted an onCompleted/onError event, or the observer has stopped listening to the observable. So this event will never be seen.

Is there a way to check if downstream handles a event a publisher publishes in Combine?

I have an event bus that publishes events happening over a custom text field(a chat message input box), like attachMediaEvent, sendMessageEvent, selectTextEvent. I want to provide provide default actions for the events, but only in case downstream doesn't handle it(kind of like how one can provide protocol default implementation). Is there a way to check if the downstream handles the event and handle it if doesn't?
I know that I'm being loose with the phrase handling the event but it was intentional and I would like any sort of solution for it. And I'm not adding any code, but I can do so, if needed. It is a really simple publisher that pushes events, anyhow.
EDIT:
Ok, I dug deeper in SO and I started to get the idea that I had to implement custom operator. But I have hit the exact same roadblock. I have no idea how a downstream can speak to the upstream about how it has handled its values. Although I tried to create a protocol(EventConsumer) that one could check if implemented by a object that is passed to the subscriber, but which felt very wrong. Any help would be a godsend.
Here's the code I tried in playground.
#objc protocol EventConsumer {
#objc optional func sendEventAction()
#objc optional func attachEventAction()
}
class DefaultEventConsumer: EventConsumer {
}
struct CustomPublisher<Upstream: Publisher>: Publisher where Upstream.Output == String, Upstream.Failure == Never {
typealias Output = String
typealias Failure = Never
var upstreamEventPublisher: Upstream
// var eventConsumer: EventConsumer
func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, String == S.Input {
let subscription = CustomSubscription<S>(/*eventHandler: eventConsumer*/)
subscription.downstream = subscriber
subscriber.receive(subscription: subscription)
upstreamEventPublisher.sink { eventString in
subscription.sendEvent(event: eventString)
}
}
}
class CustomSubscription<Downstream: Subscriber>: Subscription where Downstream.Input == String {
var downstream: Downstream?
// var eventHandler: EventConsumer
// init(eventHandler: EventConsumer) {
// self.eventHandler = eventHandler
// }
func request(_ demand: Subscribers.Demand) {
}
func cancel() {
downstream = nil
}
func sendEvent(event: String) {
//How do I check if downstream processes the event somehow and send event if it doesnt
downstream?.receive(event)
}
}
//
//
//extension AnyPublisher where Output == String, Failure == Never {
//
// func checkAndPublishEvents(_ eventConsumer: EventConsumer) -> Self {
//
// CustomPublisher(upstreamEventPublisher: self/*, eventConsumer: eventConsumer*/)
// .eraseToAnyPublisher()
// }
//}

RxSwift How to subscribe a observable only one time?

I want to subscribe to an observable, but in some logic, I will re-subscribe to it. If I don't want to write some special logic for it, how can I dispose the last subscription when I add a new one? Or, when I subscribe to it, how can I know if this observable has been subscribed to already?
The simplest solution for what you are looking for is indeed the method that they provided for just that - func take(_ count: Int).
Here is a playground sample:
import RxSwift
var variable = Variable(1)
variable.asObservable().take(1).subscribe(
onNext: { print("onNext: \($0)") },
onError: { print("onError: \($0)") },
onCompleted: { print("onCompleted") },
onDisposed: { print("onDisposed") })
variable.value = 2
print("done")
The results are:
onNext: 1
onCompleted
onDisposed
done
And yes, this is useful in places where you want to filter an event through a stream, in a location where it is awkward to store a subscription.
Why do you want to do that? What problem are you specifically trying to solve?
The usual way to dispose subscriptions is to use a dispose bag.
func subscribeMyObservable()
{
disposeBag = DisposeBag()
myObservable
.subscribe(onNext: { print("Whatever") }
.addDisposableTo(disposeBag)
}
Notice how I recreate the disposable every time before subscribing? That would get rid of the previous subscriptions.
.switchLatest() operator is your friend
let eventObservableWrapper = PublishSubject<Observable<MyEvent>>()
let eventObservable = eventObservableWrapper.switchLatest() // use this one for subscriptions
// to switch to another observable, call -
eventObservableWrapper.onNext(someNewEventObservable)
see more about how switch works here - http://reactivex.io/RxJava/javadoc/rx/Observable.html#switchOnNext(rx.Observable)
What do you want to do if IĀ“m not wrong is to subscribe and not unsubscribe once the items are emitted.
If thatĀ“s what you want you can use relay which you never gets unsubscribe.
/**
* ReplayRelay it works just like hot observables, once that an observer subscribe, the Relay will replay all items already emitted
* to another observer.
* it should return 1,2,3,4,5 for both observers.
*/
#Test
public void testReplayRelay() {
ReplayRelay<String> relay = ReplayRelay.create();
relay.subscribe(result -> System.out.println("Observer1:" + result));
relay.call("1");
relay.call("2");
relay.call("3");
relay.subscribe(result -> System.out.println("Observer2:" + result));
relay.call("4");
relay.call("5");
}
You can see more exmaples here https://github.com/politrons/reactive/blob/master/src/test/java/rx/relay/Relay.java
I did something similar to what #Odrakir proposed.
I came across the same problem and still haven't found a better solution than this.
let disposeBag = DisposeBag()
var publishSubject = PublishSubject<Int>()
var count = 0
func setRx(toggle: Bool) {
let tempSubject = PublishSubject<Int>()
if toggle {
tempSubject.subscribe(onNext: { n in
self.count = n
}).disposed(by: disposeBag)
} else {
tempSubject.dispose()
}
publishSubject = tempSubject
}

RxSwift state changes trigger "Warning: Recursive call or synchronization error!"

I've inherited some Swift 3 code which uses RxSwift to manage a store. The basic layout of the class is:
class UserActivityStore {
fileprivate lazy var internalDataCache: Variable<Set<NSUserActivity>?> = Variable(nil)
func addAction(_ actionToAdd: NSUserActivity) {
var content = internalDataCache.value ?? Set<NSUserActivity>()
content.insert(actionToAdd)
internalDataCache.value = content
}
func resolveAction(_ action: NSUserActivity) {
var content = internalDataCache.value
_ = content?.remove(action)
internalDataCache.value = content
}
func expectActions(_ filter: #escaping ((NSUserActivity) -> Bool)) -> Observable<NSUserActivity> {
let observable = Observable<NSUserActivity>.create { (observer) -> Disposable in
return self.internalDataCache.asObservable().subscribeNext { (newActions) in
guard let newActions = newActions else { return }
for eachAction in newActions where filter(eachAction) {
observer.onNext(eachAction)
self.resolveAction(eachAction)
}
}
}
return observable
}
}
When an action is added to this, it adds the item to the set correctly. However, the observer (in expectActions) catches that change and resolves it. Since this is all in a single thread, the error "Warning: Recursive call or synchronization error!" is triggered.
I think this is a perfectly legitimate error and that RxSwift is probably correct in its handling. However, I can't help thinking that this is a bad model. The code is essentially handling a queue of NSUserActivity objects itself.
Is this genuinely a modelling error / abuse of RxSwift or is my limited understanding of RxSwift misunderstanding this? As a hacky solution, I've tried replacing the resolveAction function with a single line internalDataCache.value?.remove(action) but that still triggers the observable and hence the bug.
Changing the observable to use a different queue (Serial or Concurrent dispatch) fixes the problem but I'm not convinced its the correct fix.

Get only one value from Observable

I am starting in RxSwift, coming from ReactiveCocoa. I have a conceptual question.
Let's say I have a value I want to observe over time, e.g. a temperatue. So there are many cases and places I subscribe this value to react on changes. No problem!
But there are also use cases when I just need the latest value e.g.:
if temperatue > 5 {
// do something
}
So i just want to do a decision/operation on that value or at least based on that value. That drives me close to using a shareReplay observable. But would I need to subscribe that value even when I just want to use it once?
Or is this approach wrong at all? How would I do that use case (value over time vs. accessing last value only once)? Would I need to sources, one hot one cold?
Use Variable:
class SomeClass {
let temperature = Variable<Int>(50)
func doSomething() {
if temperature.value > 50 {
print("something")
}
}
func subscribeToTemperature() {
temperature.asObservable.subscribeNext { t in
print("Temperature now is \(t)")
}.addDisposableTo(bag)
}
func setTemperature() {
temperature.value = 20
}
func observeTemperature(t: Observable<Int>) {
t.bindTo(temperature).addDisposableTo(bag)
}
}