RxSwift 和 MVVM 声明输出的最佳实践 Variable/Function
RxSwift with MVVM Best Practice Declaring Output Variable/Function
我将 RxSwift 与 MVVM 结合使用,我发现自己有点困惑。原因如下:
我现在的代码
视图模型
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 { ![=11=].isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: [=11=], forKey: "name")! as? Gnome else { return nil }
return gnome
}
}
let viewDidLoadProperty = Variable<String>("")
func viewDidLoad(with name: String) {
viewDidLoadProperty.value = name
}
}
ViewController
我绑定如下:
func bindViewModel() {
viewModel.outputs.gnomeObject
.subscribe { observable in self.populate(with: observable.element != nil ? observable.element! : nil) }
.addDisposableTo(viewModel.disposeBag)
}
这很好。它完美地工作(至少如预期的那样)。
但是,我在阅读以下书籍时:https://victorqi.gitbooks.io/rxswift/content/tips.html
在提示部分它说:
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.
阅读后,我对 ViewModel 进行了如下更改:
ViewModel(已编辑)
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 { ![=13=].isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: [=13=], forKey: "name")! as? Gnome else { return nil }
return gnome
}
}
}
ViewModels 的不同之处在于 GnomeObject 声明,其中一个是 var
而在“已编辑”中是一个 func
。
我担心的是,每次从 ViewController
调用 gnomeObject()
时,它都会创建一个新的 observable 实例。
在这种情况下,最佳做法应该是什么?
当他们说你应该使用纯函数时,他们的意思是函数(如果可能)应该对同一组输入具有相同的输出,这意味着,如果一个函数被调用两次并使用同一组输入,它应该 return同样的事情两次
这意味着您没有任何函数调用者不知道的隐藏可变状态(例如,拥有该方法的 class 中的 属性)。一切都应该尽可能明确。
所以,当涉及到函数时,您应该注意这一点。但是使用属性是完全可以的,就像你在第一段代码中所做的那样,它们不适用于此。
嗯,在第一个版本中,gnomeObject
是一个 let,而不是一个 var。一旦设置,就永远不会更改为不同的对象。
在第二个版本中 gnomeObject()
returns 每次调用时 不同的对象 。所以这实际上打破了 "pure function" 范式。 (注意:如果 Observable 是结构而不是 class 那么情况就不是这样了,因为结构没有标识。)
您的第一个示例遵循纯函数概念,而您的第二个版本破坏了它。
如果您希望消除在初始化程序中实例化 gnomeObject 的需要,您可以修改第一个示例以使用惰性变量,如下所示:
lazy var gnomeObject: Observable<Gnome?> = self.viewDidLoadProperty
.asObservable()
.filter { ![=10=].isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: [=10=], forKey: "name")! as? Gnome else { return nil }
return gnome
}
我将 RxSwift 与 MVVM 结合使用,我发现自己有点困惑。原因如下:
我现在的代码
视图模型
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 { ![=11=].isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: [=11=], forKey: "name")! as? Gnome else { return nil }
return gnome
}
}
let viewDidLoadProperty = Variable<String>("")
func viewDidLoad(with name: String) {
viewDidLoadProperty.value = name
}
}
ViewController
我绑定如下:
func bindViewModel() {
viewModel.outputs.gnomeObject
.subscribe { observable in self.populate(with: observable.element != nil ? observable.element! : nil) }
.addDisposableTo(viewModel.disposeBag)
}
这很好。它完美地工作(至少如预期的那样)。 但是,我在阅读以下书籍时:https://victorqi.gitbooks.io/rxswift/content/tips.html 在提示部分它说:
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.
阅读后,我对 ViewModel 进行了如下更改:
ViewModel(已编辑)
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 { ![=13=].isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: [=13=], forKey: "name")! as? Gnome else { return nil }
return gnome
}
}
}
ViewModels 的不同之处在于 GnomeObject 声明,其中一个是 var
而在“已编辑”中是一个 func
。
我担心的是,每次从 ViewController
调用 gnomeObject()
时,它都会创建一个新的 observable 实例。
在这种情况下,最佳做法应该是什么?
当他们说你应该使用纯函数时,他们的意思是函数(如果可能)应该对同一组输入具有相同的输出,这意味着,如果一个函数被调用两次并使用同一组输入,它应该 return同样的事情两次
这意味着您没有任何函数调用者不知道的隐藏可变状态(例如,拥有该方法的 class 中的 属性)。一切都应该尽可能明确。
所以,当涉及到函数时,您应该注意这一点。但是使用属性是完全可以的,就像你在第一段代码中所做的那样,它们不适用于此。
嗯,在第一个版本中,gnomeObject
是一个 let,而不是一个 var。一旦设置,就永远不会更改为不同的对象。
在第二个版本中 gnomeObject()
returns 每次调用时 不同的对象 。所以这实际上打破了 "pure function" 范式。 (注意:如果 Observable 是结构而不是 class 那么情况就不是这样了,因为结构没有标识。)
您的第一个示例遵循纯函数概念,而您的第二个版本破坏了它。
如果您希望消除在初始化程序中实例化 gnomeObject 的需要,您可以修改第一个示例以使用惰性变量,如下所示:
lazy var gnomeObject: Observable<Gnome?> = self.viewDidLoadProperty
.asObservable()
.filter { ![=10=].isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: [=10=], forKey: "name")! as? Gnome else { return nil }
return gnome
}