I’m trying to implement the Model-View-ViewModel design pattern in my Swift 4 app. Everything I’ve read about this pattern (and MVC) says to keep non-UI code out of the ViewController. However, most tutorials have the fetching of data inside the ViewController.
When the app starts it needs to fetch data from a .plist file (and eventually a JSON file). I have the method for fetching and decoding this data in its own file (NetworkClient.swift), but I just don’t know where to put its callback. Currently it’s in my ViewController’s viewDidLoad method:
networkClient.readInData { data in
guard let data = data else { fatalError("error getting data") }
self.myViewModels = data.map { MyViewModel(data: $0) }
self.tableView.reloadData()
}
Is this the right way to go? Is there a better way? I thought about putting it in MyViewModel but I would still have to call this method from my ViewController and that seems like a lot of excess. I’m trying to learn the best Swift practices so any advice would be helpful. Thanks!
One suggestion I have for you is to have your viewDidLoad method, CALL a function inside NetworkClient.swift which RETURNS the data RECEIVED from your REST API and parsed into appropriate model objects. Keep your networking code, and all JSON parsing OUTSIDE of your ViewController. Your ViewController should have a property (which should be an array), and to which you would assign the data received from NetworkClient.swift (which also should be in the form of an array). All networking, and JSON parsing code should be inside your NetworkClient.swift class. This ensures a cleaner separation of your code. Always make sure no networking, nor JSON parsing is done in your ViewController. Your ViewController should simply call a function which in turn returns the final, finished product. When you order a pizza, you expect the pizza to be cooked. You shouldn’t be expected to do any additional cooking after it has been delivered
It’s totally fine to call data fetching methods inside a view controller, but you want to keep the actual networking logic separate.
Like @syedfa said, your view controller can have an array property that stores the data received. You could also have a public static array property in the networking client, and access it within the view controller.
Your current method called within the view controller is a bit dense, if you wanted to shorten it, you could move the closure to the networking client. Something that may help you get started is the Multicast Delegate Pattern (chapter 15), which would help you with reloading the tableView only after the data is complete. Let me know if this helps!
@syedfa Thanks for the analogy! It makes this design pattern make a lot more sense
@jstrawn Thank you for the detailed answer, I ended up moving the closure to NetworkClient so that the ViewController only gets the returned data in its final form via a delegate.