Reputation: 861
I'm using RxSwift with MVVM and I'm found myself a bit confused. Here's why:
internal protocol DetailViewModelInput {
func viewDidLoad(with name: String)
}
internal protocol DetailViewModelOutput {
var gnomeObject: Observable<Gnome?> { get }
}
struct DetailViewModel: DetailViewModelType, DetailViewModelInput, DetailViewModelOutput {
let disposeBag = DisposeBag()
let gnomeObject: Observable<Gnome?>
init() {
gnomeObject = viewDidLoadProperty
.asObservable()
.filter { !$0.isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: $0, forKey: "name")! as? Gnome else { return nil }
return gnome
}
}
let viewDidLoadProperty = Variable<String>("")
func viewDidLoad(with name: String) {
viewDidLoadProperty.value = name
}
}
I make the binding as follows:
func bindViewModel() {
viewModel.outputs.gnomeObject
.subscribe { observable in self.populate(with: observable.element != nil ? observable.element! : nil) }
.addDisposableTo(viewModel.disposeBag)
}
And this is "fine". It works perfectly (at least as expected). But, I while reading the following book: https://victorqi.gitbooks.io/rxswift/content/tips.html In the tips section it says:
Always strive to model your systems or their parts as pure functions. Those pure functions can be tested easily and can be used to modify operator behaviors.
And after reading it I'm changed my ViewModel as follows:
internal protocol DetailViewModelInput {
func viewDidLoad(with name: String)
}
internal protocol DetailViewModelOutput {
func gnomeObject() -> Observable<Gnome?>
}
protocol DetailViewModelType {
var disposeBag: DisposeBag { get }
var inputs: DetailViewModelInput { get }
var outputs: DetailViewModelOutput { get }
}
struct DetailViewModel: DetailViewModelType, DetailViewModelInput {
let disposeBag = DisposeBag()
let viewDidLoadProperty = Variable<String>("")
func viewDidLoad(with name: String) {
viewDidLoadProperty.value = name
}
}
// MARK: DetailViewModelOutput
extension DetailViewModel: DetailViewModelOutput {
func gnomeObject() -> Observable<Gnome?> {
return viewDidLoadProperty
.asObservable()
.filter { !$0.isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: $0, forKey: "name")! as? Gnome else { return nil }
return gnome
}
}
}
The difference in the ViewModels is the GnomeObject declaration, in one it is a var
and in the "edited" is a func
.
My concern is, that every time gnomeObject()
gets called from the ViewController
, it will create a new instance of the observable.
What should be the best practice in this case?
Upvotes: 0
Views: 1356
Reputation: 12768
If you're looking to eliminate the need to instantiate gnomeObject in the initializer, you could modify the first example to use a lazy var like so:
lazy var gnomeObject: Observable<Gnome?> = self.viewDidLoadProperty
.asObservable()
.filter { !$0.isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: $0, forKey: "name")! as? Gnome else { return nil }
return gnome
}
Upvotes: 1
Reputation: 33967
Hmm, in the first version, gnomeObject
is a let, not a var. Once it is set, it is never changed to a different object.
In the second version gnomeObject()
returns a different object every time it's called. So this actually breaks the "pure function" paradigm. (Note: if the Observable was a struct instead of a class then this wouldn't be the case because structs don't have identity.)
Your first example follows the pure function concept while your second version breaks it.
Upvotes: 1
Reputation: 4254
When they say you should use pure functions they mean that functions (when possible) should have the same output for the same set of inputs, meaning, if a function is called twice with the same set of inputs it should return the same thing twice.
That means you don't have any hidden mutable state that the caller of the functions is not aware of (a property in the class that owns the method, for example). Everything should be as explicit as possible.
So, it's something you should be aware of when it comes to functions. But it's completely ok to use properties, as you were doing in the first code, they don't apply to this.
Upvotes: 0