【问题标题】:RxSwift with MVVM Best Practice Declaring Output Variable/FunctionRxSwift 与 MVVM 最佳实践声明输出变量/函数
【发布时间】:2017-08-11 15:06:57
【问题描述】:

我将 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 { !$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
  }
  
}

视图控制器

我按如下方式进行绑定:

  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 在提示部分它说:

始终努力将您的系统或其部分建模为纯函数。这些纯函数可以很容易地测试并用于修改操作员的行为。

阅读后,我将 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 { !$0.isEmpty }
      .map { guard let gnome = Gnome
        .fetch(uniqueValue: $0, forKey: "name")! as? Gnome else { return nil }
        return gnome
    }
  }
}

ViewModel 的区别在于 GnomeObject 声明,其中一个是 var,而在“已编辑”中是 func。 我担心的是,每次从 ViewController 调用 gnomeObject() 时,它都会创建一个新的 observable 实例。

在这种情况下,最佳做法应该是什么?

【问题讨论】:

    标签: swift mvvm rx-swift


    【解决方案1】:

    嗯,在第一个版本中,gnomeObject 是一个 let,而不是一个 var。一旦设置,它就永远不会更改为不同的对象。

    在第二个版本中,gnomeObject() 每次调用时都会返回一个不同的对象。所以这实际上打破了“纯函数”范式。 (注意:如果 Observable 是一个结构而不是一个类,那么情况就不是这样了,因为结构没有标识。)

    您的第一个示例遵循纯函数概念,而您的第二个版本打破了它。

    【讨论】:

      【解决方案2】:

      如果您希望消除在初始化程序中实例化 gnomeObject 的需要,您可以修改第一个示例以使用惰性 var,如下所示:

      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
      }
      

      【讨论】:

        【解决方案3】:

        当他们说你应该使用纯函数时,他们的意思是函数(如果可能)应该对相同的输入集具有相同的输出,这意味着,如果使用相同的输入集调用一个函数两次,它应该返回相同的结果两次。

        这意味着您没有任何函数调用者不知道的隐藏可变状态(例如,拥有该方法的类中的属性)。一切都应该尽可能明确。

        因此,在涉及功能时,您应该注意这一点。但是使用属性是完全可以的,就像你在第一个代码中所做的那样,它们不适用于这个。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-07-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多