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

Swift:ViewModel应该是结构还是类?,swift,mvvm,struct,closures,Swift,Mvvm,Struct,Closures,我正在尝试在我的新项目中使用MVVM模式。第一次,我将所有视图模型都创建为struct。但当我使用闭包实现异步业务逻辑(如fetchDataFromNetwork)时,闭包捕获旧的视图模型值,然后更新为该值。不是新的视图模型值 这里是一个在操场上的测试代码 import Foundation import XCPlayground struct ViewModel { var data: Int = 0 mutating func fetchData(completion:()-&g

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

这里是一个在操场上的测试代码

import Foundation
import XCPlayground

struct ViewModel {
  var data: Int = 0

  mutating func fetchData(completion:()->()) {
    XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://stackoverflow.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中的视图模型没有新值10


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

您应该使用一个类

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

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://stackoverflow.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\u async(dispatch\u get\u main\u queue()){…}

您可以通过两个选项获取
self.data
:或者在闭包中为
fetchResponse
(使用
viewModel
作为
结构)或者您可以创建自己的set方法/闭包,并在
init
方法中使用它(使用
viewModel
作为
class

控制台打印:

viewModel.data in didSet : 0
viewModel.data before fetch : 0
viewModel.data in fetchResponse : 10
viewModel.data after fetch : 10
他这样说:

首次初始化属性时不调用willSet和didSet观察程序。仅当在初始化上下文之外设置属性值时,才会调用它们


所以没有办法将struct与异步API调用@Code一起使用?因为我更喜欢使用结构而不是类。@Paul(再次)编辑了我的文章。是的,这是一个糟糕的设计(在这种情况下,我应该使用类。谢谢@Code。尽管它的设计很糟糕,但使用类编写单元测试要容易得多。(模仿struct的方法确实是一项令人沮丧的任务)是的,我知道。但我更喜欢使用didSet观察器,而不是将委托或回调与fetchData函数结合使用。所以我使用struct而不是class。如果无法将struct与异步变异函数结合使用,我应该使用class。
viewModel.data in didSet : 0
viewModel.data before fetch : 0
viewModel.data in fetchResponse : 10
viewModel.data after fetch : 10