Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Should ViewModel be a struct or class?

I am trying to use MVVM pattern in my new project. First time, I created all my view model to struct. But when I implemented async business logic such as fetchDataFromNetwork with closures, closures capture old view model value then updated to that. Not a new view model value.

Here is a test code in 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://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()

Console prints

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

The problem is View Model in ViewController does not have new Value 10.

If I changed ViewModel to class, didSet not called but View Model in ViewController has new Value 10.

like image 477
Paul Avatar asked Jun 20 '16 02:06

Paul


People also ask

What should ViewModel contain?

ViewModel objects can contain LifecycleObservers , such as LiveData objects. However ViewModel objects must never observe changes to lifecycle-aware observables, such as LiveData objects.

What is a ViewModel in Swift?

ViewModel hides all asynchronous networking code, data preparation code for visual presentation, and code listening for Model changes. All of these are hidden behind a well-defined API modeled to fit this particular View. One of the benefits of using MVVM is testing.

What is difference between class and struct Swift?

In Swift, structs are value types whereas classes are reference types. When you copy a struct, you end up with two unique copies of the data. When you copy a class, you end up with two references to one instance of the data. It's a crucial difference, and it affects your choice between classes or structs.


1 Answers

You should use a class.

If you use a struct with a mutating function, the function should not perform the mutation within a closure; you should not do the following:

struct ViewModel {
  var data: Int = 0

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

If I changed ViewModel to class, didSet not called

Nothing wrong here - that's the expected behavior.


If you prefer to use struct, you can do

  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)")
    }

Also note that the closure provided to dataTaskWithURL does not run on the main thread. You might want to call dispatch_async(dispatch_get_main_queue()) {...} in it.

like image 55
Code Avatar answered Sep 27 '22 20:09

Code