Swift:ViewModel 应该是结构还是类?

Posted

技术标签:

【中文标题】Swift:ViewModel 应该是结构还是类?【英文标题】:Swift: Should ViewModel be a struct or class? 【发布时间】:2016-10-21 04:48:38 【问题描述】:

我正在尝试在我的新项目中使用 MVVM 模式。第一次,我创建了我所有的视图模型来构建。但是当我使用闭包实现异步业务逻辑(例如 fetchDataFromNetwork)时,闭包会捕获旧的视图模型值,然后更新为该值。不是新的视图模型值。

这是 Playground 中的测试代码。

import Foundation
import XCPlayground

struct ViewModel 
  var data: Int = 0

  mutating func fetchData(completion:()->()) 
    XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://***.com")!) 
      result in
      self.data = 10
      print("viewModel.data in fetchResponse : \(self.data)")
      completion()
      XCPlaygroundPage.currentPage.finishExecution()
      .resume()
  


class ViewController 
  var viewModel: ViewModel = ViewModel() 
    didSet 
      print("viewModel.data in didSet : \(viewModel.data)")
    
  

  func changeViewModelStruct() 
    print("viewModel.data before fetch : \(viewModel.data)")

    viewModel.fetchData 
      print("viewModel.data after fetch : \(self.viewModel.data)")
    
  


var c = ViewController()
c.changeViewModelStruct()

控制台打印

viewModel.data before fetch : 0
viewModel.data in didSet : 0
viewModel.data in fetchResponse : 10
viewModel.data after fetch : 0

问题是 ViewController 中的 View Model 没有新的 Value 10。

如果我将 ViewModel 更改为 class,则不会调用 didSet,但 ViewController 中的 View Model 具有新值 10。

【问题讨论】:

【参考方案1】:

你应该使用一个类。

如果您使用带有变异函数的结构,则该函数不应在闭包内执行变异;您应该执行以下操作:

struct ViewModel 
  var data: Int = 0

  mutating func myFunc() 
      funcWithClosure() 
          self.data = 1
      
  

如果我将 ViewModel 更改为 class,则不会调用 didSet

这里没有错 - 这是预期的行为。


如果你喜欢使用struct,你可以这样做

  func fetchData(completion: ViewModel ->()) 
    XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://***.com")!) 
      result in
      var newViewModel = self
      newViewModel.data = 10
      print("viewModel.data in fetchResponse : \(self.data)")
      completion(newViewModel)
      XCPlaygroundPage.currentPage.finishExecution()
      .resume()
  


  viewModel.fetchData  newViewModel in
     self.viewModal = newViewModel
      print("viewModel.data after fetch : \(self.viewModel.data)")
    

还要注意,提供给dataTaskWithURL 的闭包不会在主线程上运行。您可能想在其中调用dispatch_async(dispatch_get_main_queue()) ...

【讨论】:

所以没有办法使用带有异步API调用@Code的struct?因为我更喜欢使用结构而不是类。 @Paul 编辑了我的帖子(再次)。 是的,这是一个糟糕的设计。 :( 在这种情况下我应该使用类。谢谢@Code。 尽管它的设计很糟糕,但使用类,编写单元测试要容易得多。 (struct 的 Mocking 方法确实是一项令人沮丧的任务)【参考方案2】:

您可以通过两种方式获得self.data:或者在闭包中为fetchResponse 使用返回参数(使用viewModel 作为struct)或者您可以创建自己的设置方法/闭包并使用它在你的init 方法中(使用viewModel 作为class)。

class ViewModel 
var data: Int = 0
func fetchData(completion:()->()) 
    XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://***.com")!) 
        result in
        self.data = 10
        print("viewModel.data in fetchResponse : \(self.data)")
        completion()
        XCPlaygroundPage.currentPage.finishExecution()
        .resume()
    


class ViewController 
    var viewModel: ViewModel!  didSet  print("viewModel.data in didSet : \(viewModel.data)")  

    init( viewModel: ViewModel ) 
        // closure invokes didSet
        ( self.viewModel = viewModel )()
    

    func changeViewModelStruct() 
        print("viewModel.data before fetch : \(viewModel.data)")

        viewModel.fetchData 
            print("viewModel.data after fetch : \(self.viewModel.data)")
        
    


let viewModel = ViewModel()
var c = ViewController(viewModel: viewModel)
c.changeViewModelStruct()

控制台打印:

viewModel.data in didSet : 0
viewModel.data before fetch : 0
viewModel.data in fetchResponse : 10
viewModel.data after fetch : 10

Apple Document 是这样说的:

第一次初始化属性时不会调用 willSet 和 didSet 观察者。仅当属性的值在初始化上下文之外设置时才会调用它们。

【讨论】:

是的,我知道。但我更喜欢使用 didSet 观察者而不是使用 fetchData 函数进行委托或回调。所以我使用结构而不是类。如果没有办法使用带有异步变异功能的结构,我应该使用类。

以上是关于Swift:ViewModel 应该是结构还是类?的主要内容,如果未能解决你的问题,请参考以下文章

ViewModel 和 Controller 有啥区别?

如何在 swift iOS 中使用 swift 高阶函数从本地 json 创建 ViewModel

谁应该在 Swift 上将 DisposeBag 保留在 MVVM(+controller) 中

UIView在swift中使用viewModel回调初始化

iOS 中使用 MVVM,复杂的 Cell 的 ViewModel 应该如何去写

在 Swift 中创建类和结构来表示 JSON 对象