BehaviorSubject vs Variable vs other subjects. Public and private read-write/read-only properties. #487

Closed
zdenektopic opened this Issue Feb 16, 2016 · 16 comments

Comments

Projects
None yet
9 participants
@zdenektopic

Hello.

I will start with what I know.

If I understand it correctly, Variable is type that allows storing value (read-write access) and wraps BehaviorSubject which I have no idea what is for. And Observable is type that allows read-only access.

My issue is regarding combineLatest operator which in Rx.playground allows to combine multiple BehaviorSubjects, but not Variables.Variables can be also Observables vis asObservable() method. Is there any difference between BehaviorSubject and Variable? Which should I use for common storing of values and objects in my UIViewControllers? Or should I use other Subject?
What if I want to have publicly read-only Variable/other subject/Observable and other read-write also public?

Another thing is Driver or other Units. Should I Always use Driver when working with UI elements? How? Should I have Variable or other Subject and use asDriver() every time I need all shit going on UI/main thread? In ReactiveWeatherExample there is not Driver used in UIViewController and it is instead manually ensured, the code executes on UI thread.
For example in Bond you use Observable type to for all - storing values and subscribing for events/new values.

My current code with combineLatest:

        let varA = Variable(23)
        let varB = Variable(false)
        let button = UIButton()

        Observable.combineLatest(varA.asObservable(), varB.asObservable()) { (a, b) -> Bool in
            if a > 0 && b == true {
                return true
            }

            return false
        }
            .bindTo(button.rx_enabled)
            .addDisposableTo(disposeBag)

@zdenektopic zdenektopic changed the title from BehaviorSubject vs Variable vs other subjects to BehaviorSubject vs Variable vs other subjects. Public and private read-write/read-only properties. Feb 16, 2016

@kzaher

This comment has been minimized.

Show comment
Hide comment
@kzaher

kzaher Feb 17, 2016

Member

*Subject are more intended as a way to customize behaviors of certain operators.

  • they are aligned with other Rx implementations
  • BehaviorSubject can be used in similar scenarios as Variable

Variable exists because people usually have a hard time finding BehaviorSubject and Swift has additional complexity of memory management.

  • Variable is just a thin wrapper around a private instance of BehaviorSubject
  • Variable doesn't have an interface that enables erroring out observable sequence, so that's additional compile time guarantee vs using a BehaviorSubject.
  • Variable will also complete sequence when it's deallocated and BehaviorSubject won't.

The reason why variable needs to be transformed using asObservable interface is because in that way we can assure that you can do this:

        let varA = Variable(23)
        let varB = Variable(false)
        let button = UIButton()

        _ = Observable.combineLatest(varA.asObservable(), varB.asObservable()) { (a, b) -> Bool in
            if a > 0 && b == true {
                return true
            }

            return false
        }
            .bindTo(button.rx_enabled)
            // .addDisposableTo(disposeBag) <-- no need to add to dispose bad for this particular case

This is a good and a bad thing.

  • good thing is that you don't need that dispose bad
  • the bad thing is that people will think this is a good pattern (and it's not because when you expose subject, you are loosing the best parts of Rx, declarative nature of code you can produce)
  • sometimes there is no way around this pattern because of cell reusing and similar things, but this is something to be avoided if possible (except for playing around cases, etc.)

So in short:

  • when I think of subject -> I think more of operator customization (multicast)
  • when I think of variable -> I would probably use it to expose stateful interface because I have additional compile time guarantees (although you can use BehaviorSubject if you like)

The reason why we have created Driver is to help people use the compiler to prove certain properties of their programs. I would personally use it to model stateful abstractions in the UI layer. That's why we've created it.

I would also suggest people to create their own abstractions that express properties they require and wrap observables. We've been recently asked will there be units like Single in this project. That's another excellent example of highly useful unit, but we won't implement it directly in project, although I can see myself creating a small Single unit, or someone else creating it, and publishing it to github.

My advice is to use the compiler as much as you can, but how individuals use it, it's up to them.

I wouldn't want to comment on approaches that external libraries (like Bond) or example apps (ReactiveWeatherExample) take because we haven't been related in any way with them.

We've tried to add a lot of example usages to RxExample app, so if there is some question about some of examples there, I would be happy to answer them.

Member

kzaher commented Feb 17, 2016

*Subject are more intended as a way to customize behaviors of certain operators.

  • they are aligned with other Rx implementations
  • BehaviorSubject can be used in similar scenarios as Variable

Variable exists because people usually have a hard time finding BehaviorSubject and Swift has additional complexity of memory management.

  • Variable is just a thin wrapper around a private instance of BehaviorSubject
  • Variable doesn't have an interface that enables erroring out observable sequence, so that's additional compile time guarantee vs using a BehaviorSubject.
  • Variable will also complete sequence when it's deallocated and BehaviorSubject won't.

The reason why variable needs to be transformed using asObservable interface is because in that way we can assure that you can do this:

        let varA = Variable(23)
        let varB = Variable(false)
        let button = UIButton()

        _ = Observable.combineLatest(varA.asObservable(), varB.asObservable()) { (a, b) -> Bool in
            if a > 0 && b == true {
                return true
            }

            return false
        }
            .bindTo(button.rx_enabled)
            // .addDisposableTo(disposeBag) <-- no need to add to dispose bad for this particular case

This is a good and a bad thing.

  • good thing is that you don't need that dispose bad
  • the bad thing is that people will think this is a good pattern (and it's not because when you expose subject, you are loosing the best parts of Rx, declarative nature of code you can produce)
  • sometimes there is no way around this pattern because of cell reusing and similar things, but this is something to be avoided if possible (except for playing around cases, etc.)

So in short:

  • when I think of subject -> I think more of operator customization (multicast)
  • when I think of variable -> I would probably use it to expose stateful interface because I have additional compile time guarantees (although you can use BehaviorSubject if you like)

The reason why we have created Driver is to help people use the compiler to prove certain properties of their programs. I would personally use it to model stateful abstractions in the UI layer. That's why we've created it.

I would also suggest people to create their own abstractions that express properties they require and wrap observables. We've been recently asked will there be units like Single in this project. That's another excellent example of highly useful unit, but we won't implement it directly in project, although I can see myself creating a small Single unit, or someone else creating it, and publishing it to github.

My advice is to use the compiler as much as you can, but how individuals use it, it's up to them.

I wouldn't want to comment on approaches that external libraries (like Bond) or example apps (ReactiveWeatherExample) take because we haven't been related in any way with them.

We've tried to add a lot of example usages to RxExample app, so if there is some question about some of examples there, I would be happy to answer them.

@zdenektopic

This comment has been minimized.

Show comment
Hide comment
@zdenektopic

zdenektopic Feb 17, 2016

OK, I think I am a little bit closer to understanding the whole concept.

Another question popped in my head while reading your answer. What if I would like to implement something like Promise pattern? Meaning I have some async tasks that I need to have executed serially dependent on each other, obviously I can achieve this by concat I suppose? But how could I implement something a little bit cleaner for chaining tasks, or could you point in some direction or to some example of chaining Observables that run and complete serially?

I see, Variable is sort of storage type like variable or constant in Swift. It definitely looks better and more clean in code than BehaviorSubject or PublishSubject for example.

What is best practice in FRP with RXSwift then? Having all properties in classes, controllers, view models as Subjects and/or Units?

OK, I think I am a little bit closer to understanding the whole concept.

Another question popped in my head while reading your answer. What if I would like to implement something like Promise pattern? Meaning I have some async tasks that I need to have executed serially dependent on each other, obviously I can achieve this by concat I suppose? But how could I implement something a little bit cleaner for chaining tasks, or could you point in some direction or to some example of chaining Observables that run and complete serially?

I see, Variable is sort of storage type like variable or constant in Swift. It definitely looks better and more clean in code than BehaviorSubject or PublishSubject for example.

What is best practice in FRP with RXSwift then? Having all properties in classes, controllers, view models as Subjects and/or Units?

@kzaher

This comment has been minimized.

Show comment
Hide comment
@kzaher

kzaher Feb 18, 2016

Member

The answer to first question is actually flatMap instead of concat :)

All of these things you have mentioned are equivalent to observable sequences, and thus composable. I would probably prefer Variable over BehaviorSubject because of the mentioned compile time guarantees.

We've tried to explain and provide examples how one could best use other concepts in RxExample app. I'm not sure there is a better way how I can explain where to use which concept other then those examples and all documentation we have.

The general rules of thumb are:

  • use observable sequences (Observable, Driver) as much as possible because they are pure definitions and immutable. If you expose Variable/Subjects, then you lose declarative nature of computation since anyone can publish values from any part of the system (anyone can call on* and thus cause unanticipated effects because they have an observer interface also).
  • If you think of some part in terms of data flows that drive your app, maybe using Driver is more natural then using Observable sequences in raw form since it communicates intent better, shares computation resources (like you would intuitively expect) and has additional compile time guarantees (main thread, can't error out).
  • Sometimes you don't have a choice but to use Variable/Subjects because there are things like cell reusing, you need to use some legacy code or interact with some messy part of system, etc .... And sometimes it's obvious how can values be set, so it's maybe ok-ish, but try to use them in a few places as possible.
Member

kzaher commented Feb 18, 2016

The answer to first question is actually flatMap instead of concat :)

All of these things you have mentioned are equivalent to observable sequences, and thus composable. I would probably prefer Variable over BehaviorSubject because of the mentioned compile time guarantees.

We've tried to explain and provide examples how one could best use other concepts in RxExample app. I'm not sure there is a better way how I can explain where to use which concept other then those examples and all documentation we have.

The general rules of thumb are:

  • use observable sequences (Observable, Driver) as much as possible because they are pure definitions and immutable. If you expose Variable/Subjects, then you lose declarative nature of computation since anyone can publish values from any part of the system (anyone can call on* and thus cause unanticipated effects because they have an observer interface also).
  • If you think of some part in terms of data flows that drive your app, maybe using Driver is more natural then using Observable sequences in raw form since it communicates intent better, shares computation resources (like you would intuitively expect) and has additional compile time guarantees (main thread, can't error out).
  • Sometimes you don't have a choice but to use Variable/Subjects because there are things like cell reusing, you need to use some legacy code or interact with some messy part of system, etc .... And sometimes it's obvious how can values be set, so it's maybe ok-ish, but try to use them in a few places as possible.
@zdenektopic

This comment has been minimized.

Show comment
Hide comment
@zdenektopic

zdenektopic Feb 18, 2016

Some exposed properties are supposed to have read-write access, Other read-only and the rest of them are private.

So possibly for public read-only case I could do something like:

private let _property = Variable(true)
public var property: Driver<Bool> {
  return _property.asDriver()
}

Would that be correct approach?

Some exposed properties are supposed to have read-write access, Other read-only and the rest of them are private.

So possibly for public read-only case I could do something like:

private let _property = Variable(true)
public var property: Driver<Bool> {
  return _property.asDriver()
}

Would that be correct approach?

@kzaher

This comment has been minimized.

Show comment
Hide comment
@kzaher

kzaher Feb 20, 2016

Member

Hi @zdenektopic ,

your suggestion will probably work, but this is not idiomatic for sure and not an approach I would suggest people to take.

This is one of the examples in the example app:

https://github.com/ReactiveX/RxSwift/blob/master/RxExample/RxExample/Examples/GitHubSignup/UsingDriver/GithubSignupViewModel2.swift

It also has an explanation

 /**
         Notice how no subscribe call is being made. 
         Everything is just a definition.
         Pure transformation of input sequences to output sequences.
        */

There is also the same example using vanilla observable sequences:

https://github.com/ReactiveX/RxSwift/blob/master/RxExample/RxExample/Examples/GitHubSignup/UsingVanillaObservables/GithubSignupViewModel1.swift

These are kind of ideal examples IMHO.

Important things:

  • There are ideally no Variables, Subjects ....
  • There are no subscribe, bind, drive methods or subscriptions being made in any way.
  • Everything is just a definition
  • Because there are no subscriptions, there are no DisposeBags, etc .., no resource management is needed because all operators are already chaining disposing mechanism.
  • Everything is decoupled from the UI, there are no UIElements there, only pure logic

If you are more into Redux kind of architectures, take a look at calculator example:

https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/Calculator

Member

kzaher commented Feb 20, 2016

Hi @zdenektopic ,

your suggestion will probably work, but this is not idiomatic for sure and not an approach I would suggest people to take.

This is one of the examples in the example app:

https://github.com/ReactiveX/RxSwift/blob/master/RxExample/RxExample/Examples/GitHubSignup/UsingDriver/GithubSignupViewModel2.swift

It also has an explanation

 /**
         Notice how no subscribe call is being made. 
         Everything is just a definition.
         Pure transformation of input sequences to output sequences.
        */

There is also the same example using vanilla observable sequences:

https://github.com/ReactiveX/RxSwift/blob/master/RxExample/RxExample/Examples/GitHubSignup/UsingVanillaObservables/GithubSignupViewModel1.swift

These are kind of ideal examples IMHO.

Important things:

  • There are ideally no Variables, Subjects ....
  • There are no subscribe, bind, drive methods or subscriptions being made in any way.
  • Everything is just a definition
  • Because there are no subscriptions, there are no DisposeBags, etc .., no resource management is needed because all operators are already chaining disposing mechanism.
  • Everything is decoupled from the UI, there are no UIElements there, only pure logic

If you are more into Redux kind of architectures, take a look at calculator example:

https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/Calculator

@zdenektopic

This comment has been minimized.

Show comment
Hide comment
@zdenektopic

zdenektopic Feb 21, 2016

Thanks, helped me a lot to understand how ViewModels should be built using RxSwift! :)

If you have some commands/actions in view model that come from view controller, you have to have subscribe in the view model code, don't you?

Thanks, helped me a lot to understand how ViewModels should be built using RxSwift! :)

If you have some commands/actions in view model that come from view controller, you have to have subscribe in the view model code, don't you?

@kzaher

This comment has been minimized.

Show comment
Hide comment
@kzaher

kzaher Feb 21, 2016

Member

Hm, why do you think you need to subscribe in view model? I don't remember we've called subscribe, drive or bind in any view model in RxExample app.

Word never is a tricky word and I don't like to use it, maybe I should say almost never to be more precise :)

Member

kzaher commented Feb 21, 2016

Hm, why do you think you need to subscribe in view model? I don't remember we've called subscribe, drive or bind in any view model in RxExample app.

Word never is a tricky word and I don't like to use it, maybe I should say almost never to be more precise :)

@mingyeow

This comment has been minimized.

Show comment
Hide comment
@mingyeow

mingyeow Mar 5, 2016

This article uses behavior subjects extensively. Would you say that is not the ideal implementation?

https://medium.com/cobe-mobile/implementing-mvvm-in-ios-with-rxswift-458a2d47c33d#.oucl7alcp

mingyeow commented Mar 5, 2016

This article uses behavior subjects extensively. Would you say that is not the ideal implementation?

https://medium.com/cobe-mobile/implementing-mvvm-in-ios-with-rxswift-458a2d47c33d#.oucl7alcp

@kzaher

This comment has been minimized.

Show comment
Hide comment
@kzaher

kzaher Mar 6, 2016

Member

@mingyeow I think that code could be improved, so yes, it's probably not the most ideal implementation IMHO.

Member

kzaher commented Mar 6, 2016

@mingyeow I think that code could be improved, so yes, it's probably not the most ideal implementation IMHO.

@kzaher

This comment has been minimized.

Show comment
Hide comment
@kzaher

kzaher Mar 6, 2016

Member

We can probably close this one and reopen if needed.

Member

kzaher commented Mar 6, 2016

We can probably close this one and reopen if needed.

@kzaher kzaher closed this Mar 6, 2016

@serejahh

This comment has been minimized.

Show comment
Hide comment
@serejahh

serejahh Dec 12, 2016

Hi @kzaher!
An example with no subscribe, bind, drive looks interesting, but one thing really embarrasses me - you must have a loaded view to configure your view model. For example, if I want to create a view model out side of a view controller (e.g. build a whole module using builder pattern), it will call loadView before it's really needed. Don't you think it's a problem?

Hi @kzaher!
An example with no subscribe, bind, drive looks interesting, but one thing really embarrasses me - you must have a loaded view to configure your view model. For example, if I want to create a view model out side of a view controller (e.g. build a whole module using builder pattern), it will call loadView before it's really needed. Don't you think it's a problem?

@Vasant-Patel

This comment has been minimized.

Show comment
Hide comment
@Vasant-Patel

Vasant-Patel Mar 3, 2017

I will second to @serejahh, in real world application you most likely want to create ViewModel outside of the ViewController due to dependencies coming from somewhere else that needs to be DI'ed to the ViewModel itself before the viewDidLoad is called

For very simple applications the approach works fine when viewModel itself can be constructed without any external dependencies but thats not the case with most of the real world applications

I think for that either Subject or Variable seems the only way to model this use-case, although I would only expose them as observables and keep the rest internal to viewModel

Vasant-Patel commented Mar 3, 2017

I will second to @serejahh, in real world application you most likely want to create ViewModel outside of the ViewController due to dependencies coming from somewhere else that needs to be DI'ed to the ViewModel itself before the viewDidLoad is called

For very simple applications the approach works fine when viewModel itself can be constructed without any external dependencies but thats not the case with most of the real world applications

I think for that either Subject or Variable seems the only way to model this use-case, although I would only expose them as observables and keep the rest internal to viewModel

@iandundas

This comment has been minimized.

Show comment
Hide comment
@iandundas

iandundas Apr 10, 2017

Contributor

@serejahh @Vasant-Patel A good way to get around the need for a loaded view before initing your view model is described in this blog post - see the BaseStage implementation (the naming is different in that post but it's basically just ViewModels.)

It allows the ViewModel to be created in a closure passed to the init of the ViewController like so:

let viewController = AuthenticationLandingViewController.create { (viewController) -> AuthenticationLandingViewModelType in
   let viewModel = AuthenticationLandingViewModel(viewActions: viewController.actions)
   // any extra config of the VM here
   return viewModel
}
Contributor

iandundas commented Apr 10, 2017

@serejahh @Vasant-Patel A good way to get around the need for a loaded view before initing your view model is described in this blog post - see the BaseStage implementation (the naming is different in that post but it's basically just ViewModels.)

It allows the ViewModel to be created in a closure passed to the init of the ViewController like so:

let viewController = AuthenticationLandingViewController.create { (viewController) -> AuthenticationLandingViewModelType in
   let viewModel = AuthenticationLandingViewModel(viewActions: viewController.actions)
   // any extra config of the VM here
   return viewModel
}
@sergdort

This comment has been minimized.

Show comment
Hide comment
@sergdort

sergdort Apr 10, 2017

Collaborator

@serejahh I see what you mean. If you guys interested I've came up with this idea

Collaborator

sergdort commented Apr 10, 2017

@serejahh I see what you mean. If you guys interested I've came up with this idea

@danielchristopher1

This comment has been minimized.

Show comment
Hide comment

@sergdort this is great.

@jgongo

This comment has been minimized.

Show comment
Hide comment
@jgongo

jgongo Nov 9, 2017

I'm a bit late to the conversation, and I haven't read the blog post from @iandundas or the gist from @sergdort , but I'd like to share my current approach: I'm using Swinject and passing down a Resolver, which the view controller uses to create a view model passing the arguments required by that view model. This way you create the view model once the view is already created, and have a view model created with its needed dependencies injected. You could even pass down a Resolver configured with different providers in case you want to implement some unit or integration tests for the view controller. What do you think?

jgongo commented Nov 9, 2017

I'm a bit late to the conversation, and I haven't read the blog post from @iandundas or the gist from @sergdort , but I'd like to share my current approach: I'm using Swinject and passing down a Resolver, which the view controller uses to create a view model passing the arguments required by that view model. This way you create the view model once the view is already created, and have a view model created with its needed dependencies injected. You could even pass down a Resolver configured with different providers in case you want to implement some unit or integration tests for the view controller. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment