A topic that I have been hearing a lot about lately is the MVVM design pattern. MVVM stands for Mode-View View Model and is intended to solve some of the concerns that have resulted from using the MVC design pattern - specifically in the context of iOS. While MVVM is certainly not an iOS specific design pattern, I did conduct some further research into MVVM using iOS as my research platform because I I wanted to try and figure out if MVVM could be a design pattern that fixes some of the issues that I have run into when developing large MVC iOS applications. This article is an overview of my initial research. I must warn you if you are reading this article and are looking for a complete guide on how to implement the MVVM design pattern from start to finish then this article will not fulfill that requirement. This article is just meant to present my research thus far possibly gain feedback from other developers on my current implementation.
As with any new idea or research project you need a sample project to test out your ideas with, so I created a small single view application that can be found on my Github account here if you would like to download it and check it out as you read this article. This application contains a UITableView of articles and a UISegmentedControl to filter those articles based upon alpha ascending or date ascending of the date field. I did not include core data or tests in this research application as I figured these items would come as I firmed up the architecture a bit. My goal would be to add core data into the project and then create some XCTestCases later down the line so specific functions can be tested very easily if needed.
The Idea Behind MVVM
The idea behind MVVM in iOS is that you want to eliminate the need to pile everything into the view controller. What do I mean by this? Historically, in an MVC design pattern, the view controller has been the place where lots of items tend to collect when data is being called from the model and views are being reworked and formatted to display new or updated data. The pain point that MVVM is trying to solve is that by separating logic out into different classes the view controllers can decrease in size and the separation of concerns becomes much more effective. As an of this, formatting data coming out of the model would not be done in the view controller any more but now in something called the view model. Likewise, the view model should not be altering any user interface elements but simply formatting model data and passing it back upstream to the view controller.
This workflow of MVVM is illustrated in the diagram above where you can see that the view interacts with the view controller, and the view controller interacts with the view model, and the view model interacts with the model. That is supposed to be the desired chain of command so that the view controller is never communicating with the model and the model is never setting up and executing display logic that might require UIKit. In fact many developers will tell you that UIKit usually has no business even being imported into the view model or the model at all. In researching MVVM, keeping UIKit out of the view model and the model is something that I really find useful in regards to separating the concerns and the logic flow of specific parts of my application architecture.
One very interesting aspect of using the MVVM architecture is observing the state change of properties in the view model and updating the view controller based upon these new changes. For example, you could setup an observation using KVO (Key-Value Observing) to notify the view controller when a date associated with an object has changed thus sending a signal to the view controller to update the view with this information. That way there is a continuous flow of data back and forth between the view model and the view controller. I found this particularly interesting because it eliminates the need to add any sort of interaction with the model in your view controller. One thing that I would also like to point out is that KVO is not the only way to communicate between the view model and the view controller either. RAC and Protocols are also methods to communicate state change between the view model and view controller and as you will see in my sample application I chose to use a protocol.
One wrinkle I found in my research with MVVM is that I struggled to find an appropriate place to add in the persistent storage classes (Core Data) or network stack in a diagram like you see above. The only solution I came up with to add the persistent storage was to add this class in direct relation to the View Model. There could be an easier solution in integrating this class that I am just overlooking, so please, if you are reading this and have some suggestion on how to integrate the persistent storage or the network classes, please leave a comment because I would like to hear your thoughts.
Sample Research Project
As mentioned above, to accurately research a topic you really need a sample project to do this with, so I created a sample UITableView application that you can download from my Github account here to follow along with my research. This sample project is a small Swift 3 based utility application that display a small list of articles in a UITableView. The only user interaction is the a UISegmentedControl that allows a user to filter the table of articles by title alpha ascending or date ascending. I thought that this project would be a good foundation for me to kick off my research and then add things like Core Data, testing, and article interaction as my research continues.
The View
The first piece of the puzzle I wanted to scaffold out was the view and this is something that I largely did using the Storyboard file that comes with a single view application template. As you can see I added a UITableView, a UILabel, a UISegmentedControl, and a custom table cell called ArticleCellTableViewCell. This essentially will give us the ability to display UITableView cells using a custom format by a defined filter that is selected from the UISegmentedControl. Below is the code for the custom UITableViewCell I created.
// // ArticleCellTableViewCell.swift // MVVM Sample // // Created by Matt Eaton on 10/28/16. // Copyright © 2016 AgnosticDev. All rights reserved. // import UIKit class ArticleCellTableViewCell: UITableViewCell { // Custom UITableCell @IBOutlet weak var articleImage: UIImageView? @IBOutlet weak var articleTitle: UILabel? @IBOutlet weak var articleDate: UILabel? override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } }
The Model
The next thing that I did was I defined my model now that I have prototyped out my UITableViewCell and I knew what items I wanted to go into my cell, so I created a very basic class called Article. In my article class I have properties for title, image, and date as seen below.
// // Article.swift // MVVM Sample // // Created by Matt Eaton on 10/28/16. // Copyright © 2016 AgnosticDev. All rights reserved. // import Foundation // Simple Article Model Object class Article { var title: String var date: Date var image: Data init(title: String, date: Date, image: Data) { self.title = title self.date = date self.image = image } }
The View Model
Now that I have a view and a model I thought it would be a good time to implement a View Model also so be the communication class between the view controller and the model. The following code below if what I started off with but is now what I ended with, but I will explain that in a minute.
// // ViewModel.swift // MVVM Sample // // Created by Matt Eaton on 10/28/16. // Copyright © 2016 AgnosticDev. All rights reserved. // import Foundation class ArticleViewModel { // Article Protocol weak var articleDelegate: ArticleProtocol? // Private Article Object private var article: Article? // The title for this article var titleText: String? { // Force unwrap the title as it is an expected value return article!.title } // The formatted date for this article var dateFormattedString: String? { // Unwrap date optional to make sure there is a value if let dateVal = article?.date { let dateStringFormatter = DateFormatter() dateStringFormatter.dateFormat = "dd-MM-yyyy" return dateStringFormatter.string(from:dateVal) } else { // Could return a default value here // For sake of example return nil return nil } } // The image for this article var image: Data? { // Unwrap image optional to make sure there is a value if let imageData = article?.image { return imageData } else { // Could return a default image here // For sake of example return nil return nil } } init (article: Article) { self.article = article } }
The View Controller
Next to wire up the interaction between the Storyboard running my view and the methods that would pass data down to the model I needed to build out the rest of the view controller. The code below was my starting point but certainly not my ending point.
// // ViewController.swift // MVVM Sample // // Created by Matt Eaton on 10/28/16. // Copyright © 2016 AgnosticDev. All rights reserved. // import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, ArticleProtocol { // The filterSwitch and tableView declared as weak IBOutlets @IBOutlet weak var filterSwitch: UISegmentedControl? @IBOutlet weak var tableView: UITableView? // View Controller's local variables var localArticles: [Article]? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. viewModel?.articleDelegate = self // Trigger the initial load of data as sorted by alpha triggerSegmentationSwitch(sender: filterSwitch!) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: UISegmentedControl Callback // // Callback for the UISegmentedControl to reload the table data sorted using a specific filter // @IBAction func triggerSegmentationSwitch(sender: UISegmentedControl) { let index = sender.selectedSegmentIndex if index == 0 { } else { } } // MARK: ArticleProtocol // // Article Protocol callback function to get a message from ArticleViewModel that new data is available and ready to reload the view // func resetTableData(articleData: [Article]) { localArticles = articleData tableView!.reloadData() } // MARK: Tableview Functions // // Get the number of rows for the tableView // func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return localArticles!.count } // // Get the data from the loadArticles array based upon the row index and pluck an Article object out of the // array and wrap it in the ArticleViewModel class using the convenience constructor. // Set the ArticleViewModel formatted properties to the custom ArticleCellTableViewCell // func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let articleCell: ArticleCellTableViewCell = tableView.dequeueReusableCell(withIdentifier: "ArticleCell") as! ArticleCellTableViewCell let articleVM = ArticleViewModel(article: (localArticles?[indexPath.row])!) // Set the article image, title, and date articleCell.articleImage?.image = UIImage(data:(articleVM?.image!)!, scale:1.0) articleCell.articleTitle?.text = articleVM?.titleText articleCell.articleDate?.text = articleVM?.dateFormattedString return articleCell } // // Selection of a row // func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Display your awesome article here! } }
Next, I knew that I needed an efficient way to communicate back to the view controller from the view model and I could have used KVO or RAC to do this but for the sake of example I chose to use a small one function protocol called ArticleProtocol. This function was intended to send the data back to the view controller from the view model once a filter was selected from the UISegmentedControl.
// // ArticleProtocol.swift // MVVM Sample // // Created by Matt Eaton on 10/29/16. // Copyright © 2016 AgnosticDev. All rights reserved. // protocol ArticleProtocol: class { // Signaling function that hands the ArticleViewController new data func resetTableData(articleData: [Article]) }
So at this point you may be looking at my code and thinking that this seems to be pretty standard fare for implementing MVVM, but it was at this point in my research where I hit a serious snag because I wanted to add in some sort of class that emulated a ManagedObjectContext class like Core Data does. This really raised some questions about where was the best place to put this class? My first thought was to make the view model inherit from this class, but then that proved to be hairy so I ultimately made it a stand alone class that the view model calls out to get data and then send it back to the view controller. This is one major part of this research that I am unsure of. The same goes with the network stack, where does this fit into the MVVM design architecture?
Below is ultimately the class I built out to emulate a ManagedObjectContext. This class called ArticleData just holds all of the articles in an array.
// // ArticleData.swift // MVVM Sample // // Created by Matt Eaton on 10/28/16. // Copyright © 2016 AgnosticDev. All rights reserved. // import Foundation class ArticleData { // For sake of example this class is used to setup an array to hold articles in memory // Normally this would be some sort of persistent data storage object var articles = [Article]() func sortArticleDataAlpha() ->[Article] { return self.articles.sorted(by: { $0.title < $1.title }) } func sortArticleDataByDate() ->[Article] { return self.articles.sorted(by: { $0.date < $1.date }) } init (articles: [Article]) { self.articles = articles } }
Having at least made a decision about where I am placing the ArticleData class and how it fits into the sample project workflow I now needed to get data into this class so the sample application could call this data to be displayed, so I decided to add onto my view model, view controller, and even AppDelegate to get this job done. Below are the additions I made to my research project to make sure I could accomodate for the ActivityData class and to make sure that data was initialized with the ActivityData class once it was created.
// // AppDelegate.swift // MVVM Sample // // Created by Matt Eaton on 10/28/16. // Copyright © 2016 AgnosticDev. All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? let articleViewModel = ArticleViewModel() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Build an initial set of data to be loaded into the ArticleData class var newArticles = [Article]() // Swift Thumbnail let swiftImage = UIImage(named: "swift")! let swiftImageData: Data = UIImagePNGRepresentation(swiftImage)! print ("Swift data \(swiftImageData)") // JavaScript Thumbnail let javaScriptImage = UIImage(named: "javascript")! let javaScriptImageData: Data = UIImagePNGRepresentation(javaScriptImage)! // Python Thumbnail let pythonImage = UIImage(named: "python")! let pythonImageData: Data = UIImagePNGRepresentation(pythonImage)! // ObjC Thumbnail let appleImage = UIImage(named: "apple")! let appleImageData: Data = UIImagePNGRepresentation(appleImage)! // Setup the date formatter for the Article Objects let dateFormatter = DateFormatter() dateFormatter.dateFormat = "dd-MM-yyyy" // Create new Article objects newArticles.append(Article(title: "Great Swift Article", date: dateFormatter.date(from:"18-10-2016")!, image: swiftImageData)) newArticles.append(Article(title: "Excellent Objective-c", date: dateFormatter.date(from:"06-10-2016")!, image: appleImageData)) newArticles.append(Article(title: "Awesome Python Article", date: dateFormatter.date(from:"14-10-2016")!, image: pythonImageData)) newArticles.append(Article(title: "JavaScript Article", date: dateFormatter.date(from:"10-10-2016")!, image: javaScriptImageData)) // Initialize the ArticleData object with the new array of Article objects and set as a class property let articleData = ArticleData(articles: newArticles) articleViewModel.articleData = articleData return true } ... } // // ViewModel.swift // MVVM Sample // // Created by Matt Eaton on 10/28/16. // Copyright © 2016 AgnosticDev. All rights reserved. // import Foundation class ArticleViewModel { // Article Protocol weak var articleDelegate: ArticleProtocol? // Private Article Object private var article: Article? var articleData: ArticleData? ... // I have questions around the placement of this function // I am thinking there may be a better place for this function // This function calls out to the ArticleData object and gets a sorted array of the article data func getArticleData(withFlag: String) { // This function calls back to the View Controller not using KVO or RAC but a protocol instead if withFlag == "alpha" { articleDelegate?.resetTableData(articleData: articleData!.sortArticleDataAlpha()) } else { articleDelegate?.resetTableData(articleData: articleData!.sortArticleDataByDate()) } } // This initializer is a convenience initializer because I need to call the standard constructor in AppDelegate // to get a strong reference to this class and I need to call this convenience initializer when I want to wrap // an Article object in the properties of this class convenience init? (article: Article) { self.init() self.article = article } } // // ViewController.swift // MVVM Sample // // Created by Matt Eaton on 10/28/16. // Copyright © 2016 AgnosticDev. All rights reserved. // import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, ArticleProtocol { // The filterSwitch and tableView declared as weak IBOutlets @IBOutlet weak var filterSwitch: UISegmentedControl? @IBOutlet weak var tableView: UITableView? // View Controller's local variables weak var viewModel: ArticleViewModel? var localArticles: [Article]? var appDelegate = UIApplication.shared.delegate as! AppDelegate override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. // Take the strong reference from the AppDelegate and assign it to the view controllers weak reference of the same object viewModel = appDelegate.articleViewModel viewModel?.articleDelegate = self // Trigger the initial load of data as sorted by alpha triggerSegmentationSwitch(sender: filterSwitch!) } ... // MARK: UISegmentedControl Callback // // Callback for the UISegmentedControl to reload the table data sorted using a specific filter // @IBAction func triggerSegmentationSwitch(sender: UISegmentedControl) { let index = sender.selectedSegmentIndex if index == 0 { viewModel?.getArticleData(withFlag: "alpha") } else { viewModel?.getArticleData(withFlag: "date") } } ... }
In Conclusion
In conclusion I am very intrigued by MVVM based upon my initial research thus far. I think there are very real benefits that I can see from using this type of pattern over the course of a large project because MVVM can further separate out your concerns between the model, view model, and view controller which ultimately leads to less and cleaner code. Another great benefit that I can see coming out of MVVM is testing. I can see where it would be a lot easier to pluck certain functions out of you application and test them as opposed to including large pieces of your application in a test instead. Another benefit that I see from MVVM is that it makes you think about your architecture a bit more. I think with MVC it was easy to blur the line between different parts of your application and it was more forgiving when you did so. MVVM make you think about what you building and why you are developing classes in a way that you do instead of defaulting to adding it to the view controller.
Having said that, there are also some concerns that I have with MVVM. Some of them I have already stated, but largely I have concerns about weaving the network stack and the persistent storage layers into the MVVM. Instead of the view controller becoming very large I have concerns that the view model is going to be the part that becomes large instead. These concerns could be unrealistic as this is just my first bit of research into MVVM, but ultimately I would like to know more. If you have read this article and have some ideas to share with me about where these classes might go, please leave me a comment or get a hold of me, I would love to hear your feedback.
The project described in this post is up on my Github page here. Let me know if you have any questions. Hopefully I will be posting follow ups to this article here in the future. Thanks!
Comments
MVVM Persistent Storage
Great article! I will follow this post hoping someone can share how he handles the persistent storage in a MVVM application. I know you could access the persistent storage in your view models through a singleton object but I don't like this approach. Also, you could inject your persistent storage in each view model as you create them but I am not convinced about this approach also because some view model may not require the usage of a persistent storage but you would still inject it in because you will want to use it further down in your app. Anyways, I hope someone can share how he does it :)
Thank you very much, Charles…
Thank you very much, Charles! I appreciate the feedback. Yes, I have been researching implementing the persistent storage in a single object but I am with you that I do not exactly like this approach either. I also thought about create a persistent storage class that all view models could inherit from but I have not successfully proven this out yet either. I will keep you updated on my progress.
Github code not able to download
very nice tutorial. But i'm not able to download code from github. There is no option to clone code or download code. I think you have to set some permission for it.
Hey Sunny, thank you very…
Hey Sunny, thank you very much for that feedback! I see what you mean about the Github source download. I group my blog examples together in one repo. That is probably why you cannot download the example directly. Here is a link to the outer repo: https://github.com/agnosticdev/Blog-Examples. Let me know if you can download and get to the code from there.
Network Layer
Great article on MVVM architecture. Can you add Network layer on this project following MVVM architecture or any sample where you have used/seen it ? Most of the similar blog that I have seen on MVVM are similar to yours and they have also not used Network Layer. Currently, I am working on a project where I am using MVVM architecture but there is a mess of Network side code.
Hello Shrawan, glad you…
Hello Shrawan, glad you liked the article. Thank you.
I am actually working on a blog post that has the network layer added to it. Hopefully in the next coming weeks I will have it ready.
Place to add newtork request and database request
Since MVVM is from C# (Microsoft). So I asked the developer who have been working in .Net from years. They said that best practice will be to include in Controller part.
Business Logi
where will the business logic of app go?
There are a couple of…
There are a couple of schools of thought on this question. The first idea would be to add some of the business logic in to the model object, since this will be the object that your business logic is interacting with. Another idea would be to put a business layer between the view model and the model. That way your model files can be relatively small and the business layer can call into several model objects if it needs to.
Thanks for sharing. Where did you fit Network layer and core dat
Thanks for sharing. I am following MVVM. I am also not sure where to fit the Core data and Network layer. As of now, I have a manager for each unique feature which in turn handles the Persistence and Network calls which in turn is coordinated by ViewController to read from Core data and write to it and make network calls
RK, great question. In a…
RK, great question. In a MVVM architecture I would use the model object, in this case ArticleData object, as the NSManagedObject where the object is stored. Then from the ViewModel is where this object can be managed by the CoreData context.
In regards to the network calls, one option you could look at is thinking of your network calls like micro-services. In your ViewModel you could put the high level network call that sends the top level request and handles the top level date response. That way the data can be routed to core data in your ViewModel. The top level request in your view model could delegate sending many or just one network call to a lower level networking stack that runs many single request network calls for your entire app. These single calls would send a completion back to the viewModel with a wrapped up data set to act upon.
It's really interesting…
It's really interesting article, thanks for sharing. In my opinion MVVM have more benefits than disadvantages. The most important for me is testing and thinking about architecture a bit more. I will also follow this post and your blog because I like your article. Now I am working with my team on a project where I'm using MVVM architecture.
Thank you Natalia,…
Thank you Natalia,
I agree. Testing is a critical part to this architecture as well and possibly a follow up post I could implement here as well. I appreciate the feedback and let me know how your MVVM project goes.