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
}