Rxswift: Accessing the last value/current state of a Variable

Created on 15 May 2015  Â·  5Comments  Â·  Source: ReactiveX/RxSwift

Do you have any plans to provide a property or method on the Variable class to allow directly accessing the latest value or is there currently a mechanism for doing so? I notice in the tests that you access the latest value as follows:

var latestValue: Int?
let subscription = c >- subscribeNext { next in
latestValue = next
}

I am new to Rx, however it seems it would be useful to supply a latestValue/value property on the Variable to simplify cases in which one desires access to the value without having to subscribe. Even a read only property would prove helpful. The subscription just feels like an extra hoop to jump through. Though I do understand if directly accessing the variable is an undesirable breaking with Rx concepts.

Or does "subscribeNext" always send the latest or "last" value?

question

Most helpful comment

Hi, @carlosypunto has asked me the same thing over email. I'm just going to paste you edited correspondence with him here :)

Variable doesn’t have any property called snapshot or value because that’s conceptually wrong thing to do in general case. But unfortunately could be perfectly normal and practical in most cases. I was totally torn by this decision and I will create documentation about this.

So why was variable named Variable then. It was named that way to make it explicit that it’s hot observable (everybody understand that variables are stateful). That means that variable Observable will originally contain cached last value that will be replayed to subscribers immediately on subscription (replay subject). This is not a general property of Observables.

These are the reasons why it's like this now:

  • RxSwift should be consistent with original Rx public interfaces (I even had doubt about adding variable in the first place, but the benefits regarding code understanding for new users were significant IMHO. When I've started to use Rx it was really hard to find subject and figure out how does he fit in all of this). It's just an alias for replay subject anyway.
  • There isn't any clear good way to define that interface (explanation below)
  • It makes it convenient to use Rx in a wrong way because you can easily think that it's stateful and operators store values, when that's not how it works at all
  • Workaround is pretty easy, either adding an Variable extension with subscribeNext or adding additional variable that store state
  • I personally don't like frameworks that look easy to use, but by using them in a naive way you will shot yourself in the foot
  • If I recklessly add this to public interface, it will be probably broadly used because of imperative minds of all of us (I'm also trying to change myself), so in case I decide to change it or remove it in 2.0 version, existing users will be irritated. IMHO, it's better to politely explain why some feature is not there, then to provide it, and then remove it.

But on the other hand:

  • Adding another variable each time is irritating (I'm also irritated)
  • New users will be irritated because this is expected feature in their minds
  • Even though there are only 2 points here, they are really heavy on the scale, and can't ignore them

Right now, what I'm planning to do is to add RxExperimental project that will contain that feature. In that way new users will be able to use it and won't need to define it themselves, but it will be clearly marked and documented as experimental for now.

I'm still torn by this decision, and it is unclear what should be the correct thing to do.

Why is pulling value a bad thing in general case?

In general case it’s wrong because you could have code like this (and this is a common anti pattern I've seen in multithreaded programs)

let v = Variable<Int>(0)

v >- subscribeNext { println($0) }

dispatch_async(queue1) {
    v.next(v.value + 1)
}

dispatch_async(queue2) {
    v.next(v.value + 1)
}

We can’t know what will be printed.

It could be 0, 1, 2 or 0, 1, 1

This maybe looks like a silly reason, and if this was a framework for UI layer only I would add it in a heartbeat, but one of the most common scenarios where people (and myself) will use this framework are multithreaded systems.

In that context, this is poison served as remedy.

That being said, that doesn’t mean you can’t make your code more elegant.

This is approx what I'm thinking of adding to RxExperimental project

extension Variable {
    // because this is a snapshot of a value in time
    var snapshot : T? {
        get {
            var snapshot: T? = nil
            // This will make sure that you access variable in a thread safe manner
            // Remember, if the value is complex, like array, you must ensure that only one thread is accessing the object at a time
            // otherwise your program will crash.
            // The performance penalty for this will probably be negligible if used a real app
            self >- subscribeNext {
                snapshot = $0
            } >- scopedDispose

            return snapshot
        }
    }
}

The other possible interface I'm thinking about is

variable.mutate { oldValue in
    return // ….
}

but still unsure, maybe I'll add two of them to experimental project.

All 5 comments

Hi, @carlosypunto has asked me the same thing over email. I'm just going to paste you edited correspondence with him here :)

Variable doesn’t have any property called snapshot or value because that’s conceptually wrong thing to do in general case. But unfortunately could be perfectly normal and practical in most cases. I was totally torn by this decision and I will create documentation about this.

So why was variable named Variable then. It was named that way to make it explicit that it’s hot observable (everybody understand that variables are stateful). That means that variable Observable will originally contain cached last value that will be replayed to subscribers immediately on subscription (replay subject). This is not a general property of Observables.

These are the reasons why it's like this now:

  • RxSwift should be consistent with original Rx public interfaces (I even had doubt about adding variable in the first place, but the benefits regarding code understanding for new users were significant IMHO. When I've started to use Rx it was really hard to find subject and figure out how does he fit in all of this). It's just an alias for replay subject anyway.
  • There isn't any clear good way to define that interface (explanation below)
  • It makes it convenient to use Rx in a wrong way because you can easily think that it's stateful and operators store values, when that's not how it works at all
  • Workaround is pretty easy, either adding an Variable extension with subscribeNext or adding additional variable that store state
  • I personally don't like frameworks that look easy to use, but by using them in a naive way you will shot yourself in the foot
  • If I recklessly add this to public interface, it will be probably broadly used because of imperative minds of all of us (I'm also trying to change myself), so in case I decide to change it or remove it in 2.0 version, existing users will be irritated. IMHO, it's better to politely explain why some feature is not there, then to provide it, and then remove it.

But on the other hand:

  • Adding another variable each time is irritating (I'm also irritated)
  • New users will be irritated because this is expected feature in their minds
  • Even though there are only 2 points here, they are really heavy on the scale, and can't ignore them

Right now, what I'm planning to do is to add RxExperimental project that will contain that feature. In that way new users will be able to use it and won't need to define it themselves, but it will be clearly marked and documented as experimental for now.

I'm still torn by this decision, and it is unclear what should be the correct thing to do.

Why is pulling value a bad thing in general case?

In general case it’s wrong because you could have code like this (and this is a common anti pattern I've seen in multithreaded programs)

let v = Variable<Int>(0)

v >- subscribeNext { println($0) }

dispatch_async(queue1) {
    v.next(v.value + 1)
}

dispatch_async(queue2) {
    v.next(v.value + 1)
}

We can’t know what will be printed.

It could be 0, 1, 2 or 0, 1, 1

This maybe looks like a silly reason, and if this was a framework for UI layer only I would add it in a heartbeat, but one of the most common scenarios where people (and myself) will use this framework are multithreaded systems.

In that context, this is poison served as remedy.

That being said, that doesn’t mean you can’t make your code more elegant.

This is approx what I'm thinking of adding to RxExperimental project

extension Variable {
    // because this is a snapshot of a value in time
    var snapshot : T? {
        get {
            var snapshot: T? = nil
            // This will make sure that you access variable in a thread safe manner
            // Remember, if the value is complex, like array, you must ensure that only one thread is accessing the object at a time
            // otherwise your program will crash.
            // The performance penalty for this will probably be negligible if used a real app
            self >- subscribeNext {
                snapshot = $0
            } >- scopedDispose

            return snapshot
        }
    }
}

The other possible interface I'm thinking about is

variable.mutate { oldValue in
    return // ….
}

but still unsure, maybe I'll add two of them to experimental project.

Would adding experimental project help or do you think that's a bad idea?
Do you have some other suggestion, please let me know.

As far as possible it should be clear that it leaves the original Rx philosophy. I´m newbie, I appreciate this type of aid. But my intention is to use the library as should be done, although initially for a easier approach I see useful see the experimental project

Thanks for the detailed explanation. I would much rather improve my thinking and continue learning the Rx philosophy than see the framework compromised by shortcuts that further compromise learning and understanding while enabling bad habits.

I'll port BehaviorSubject in next release, it has the necessary functionality that is similar to what you were asking.
The arguments are still valid, so be careful when to use it. It should be fine to use it in the UI layer.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Z-JaDe picture Z-JaDe  Â·  3Comments

retsohuang picture retsohuang  Â·  3Comments

trant picture trant  Â·  3Comments

hannesstruss picture hannesstruss  Â·  3Comments

gaudecker picture gaudecker  Â·  3Comments