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

时间:2016-06-20 02:32:57

标签: swift mvvm struct closures

我正在尝试在我的新项目中使用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中的View Model没有新的Value 10。

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

2 个答案:

答案 0 :(得分:7)

你应该使用一个班级。

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

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_async(dispatch_get_main_queue()) {...}

答案 1 :(得分:1)

您可以在两个选项中获取self.data:在fetchResponse的闭包中使用return参数(使用viewModel作为struct)或者您可以创建自己的init set-method / closure并在viewModel方法中使用它(使用class作为class ViewModel { var data: Int = 0 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! { 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

控制台打印:

csv = r.text

outfile = open('/path/to/my.csv', 'w')
outfile.write(csv)
outfile.close()

Apple Document 像这样说:

  首次初始化属性时,不会调用

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