
MVVM pattern with Swift application 1/3
Why should I use MVVM (Model-View-ViewModel) with Swift application? Many iOS developers use MVC (Model-View-Controller) pattern when developing iOS applications. The problem is that you usually end up putting A LOT of code to your ViewController. This happens so often that the pattern is sometimes called a Massive-View-Controller. Part of the reason behind it is that the responsibilities are not always very clear. v view controller tends to act both as view and a controller. You end up putting all the view updating stuff and the logic in the view controller. This makes unit-testing a nightmare. It also breaks the mentality to keep things separated and dependency count minimal.
MVVM will help you with all that. In MVVM, View has a reference to the view model and view model has a reference to model and that’s it. Data state changes between the view and the view model are handled with data binding pattern. The aim is to keep the view as light as possible. View model is the place where you put all business logic and data handling. Model is the place where you store your data. You might also want to put some helper functions there to handle that data. If you do MVVM right you usually end up in a situation that the view is so simple that you don’t need to write any tests for it. All you need to do is write tests for the view model. It’s easy to test different data states of the View by creating mock data for the view model. Since there is no reference to UI-layer it is a lot easier to test view model compared to View.
This tutorial will go through the best practices that I have learned with MVVM. I’ll cover this by building a mobile client for FriendService. FriendService is a server that we built in the last post: Server-side Swift, how to set up a backend. You can either set up the backend at your localhost. Follow the instructions in the previous post. Or you can use the service I have running on Heroku.
I divided this tutorial into 3 posts. This first part concentrates on setting up the project and the model part of the MVVM-pattern. It also covers networking with proper error handling. The second part concentrates more in the view model and the view of MVVM. It also covers data state changes between them with data binding pattern. In the last part, we will add a bit more functionality by creating, updating and deleting friends.
You can get the complete codes to Friend app here: https://github.com/JussiSuojanen/friends. The source code already contains all the functionality the 3 post series will have. So there is a bit more code in many of the files compared to what we’ll cover in this first post.
Setting up the MVVM project

Friends project
- AppServerClient that will download all the data from the server.
- Model to store the data into.
Prepare CocoaPods & Alamofire
1 2 3 4 5 6 7 |
source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks! target 'Friends' do pod 'Alamofire', '~> 4.0' end |

pod install
Getting friend information
MVVM: Model
In MVVM, Model is the place where we store all the data and that is pretty much all it does. When using MVC you might write some or most of the functionality in the model but that is not the case with MVVM. AppServerClient will create instances of Friend-model so we need to implement it first. Create a file name ‘Friends’. Remove all the import statements and define a struct called Friend:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// // friend.swift // Friends // // Created by Jussi Suojanen on 09/11/16. // Copyright © 2016 Jimmy. All rights reserved. // struct Friend { let firstname: String let lastname: String let phonenumber: String let id: Int } /// Mark: - extension Friend /// Put init functions to extension so default constructor /// for the struct is created extension Friend { init?(json: JSON) { guard let id = json["id"] as? Int, let firstname = json["firstname"] as? String, let lastname = json["lastname"] as? String, let phonenumber = json["phonenumber"] as? String else { return nil } self.id = id self.firstname = firstname self.lastname = lastname self.phonenumber = phonenumber } } |
1 |
typealias JSON = Dictionary<String, Any> |
AppServerClient with Alamofire
AppServerClient is not actually part of the MVVM pattern. But I wanted the Friend app to send and receive data. So let’s concentrate for a moment for proper networking. Create a new file named AppServerClient and put it inside “Service” folder in the project. Copy the code below in it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// // AppServerClient.swift // Friends // // Created by Jussi Suojanen on 07/11/16. // Copyright © 2016 Jimmy. All rights reserved. // import Alamofire // MARK: - AppServerClient class AppServerClient { // MARK: - GetFriends enum GetFriendsFailureReason: Int, Error { case unAuthorized = 401 case notFound = 404 } typealias GetFriendsResult = Result<[Friend], GetFriendsFailureReason> typealias GetFriendsCompletion = (_ result: GetFriendsResult) -> Void func getFriends(completion: @escaping GetFriendsCompletion) { Alamofire.request("http://friendservice.herokuapp.com/listFriends") .validate() .responseJSON { response in switch response.result { case .success: guard let jsonArray = response.result.value as? [JSON] else { completion(.failure(nil)) return } completion(.success(payload: jsonArray.flatMap { Friend(json: $0 ) })) case .failure(_): if let statusCode = response.response?.statusCode, let reason = GetFriendsFailureReason(rawValue: statusCode) { completion(.failure(reason)) } completion(.failure(nil)) } } } } |
That is a big chunk of code to chew at ones but try to break it down step by step.
Now there are 4 important things that you need to focus here:
- GetFriendsCompletion
- Result
- GetFriendsResult
- GetFriendsFailureReason
GetFriendsCompletion
GetFriendsCompletion is a function parameter which takes GetFriendsResult as a parameter. It is defined as an @escaping function which means it has a strong reference to every object it captures. It also means you have to be careful not to introduce memory issues while calling it, but that will be covered in more detail later on when it is actually called. More information about @escaping vs @non-escaping functions here: Optional Non-Escaping Closures.
Result
1 2 3 4 |
enum Result<T, U> where U: Error { case success(payload: T) case failure(U?) } |
GetFriendsResult
We have defined GetFriendsResult as Result. Payload parameter in success case is defined as type [Friend]. and failure case parameter is defined as type GetFriendsFailureReason.
GetFriendsFailureReason
GetFriendsFailureReason is an enum which raw value is Int. Since we defined it as an Int, we can map it from HTTP-status codes to cases defined in the enum. This makes them a lot more readable. GetFriendsFailureReason conforms to Error protocol. This means which we can use it as a parameter in the Results failure case.
Alamofire
Alamofire is HTTP networking library written in Swift. It simplifies the HTTP networking with iOS applications. AppServerClient is not a complete example of Alamofire networking. For example, this version uses default SessionManager and HTTP-headers. You might want to set up those your self in a real project. Because the backend is simple and we will never use the code in production, this setup is enough for the task at hand.
Request
Http-request is done with ‘request’-function which takes UrlConvertable as a parameter. UrlConvertable is an Alamofire protocol. It guarantees that the type that conforms the protocol can be constructed to URL. Alamofire also extends String to conform UrlConvertable so we can use a string as a parameter.
Validate
Alamofire also supports chaining. So we can call validate() and responseJSON right after the original request. Alamofire 4.0 takes “There are no failures, just results” -thinking seriously. So we need to call validate() to know if the request was a success (status code 200 – 299) or a failure. The response is JSON, we can use responseJSON handlers’ completion block to handle the response.
Handling the response
In the completion block use switch to get the result of the data response. Result is almost identical the Result defined earlier. With the exception that it only takes 1 generic parameter. In AppServerClient callback function, you want to handle the error a bit closer to UI. So we need to define Result with 2 Generics (one of which conforms to Error). This way also GetFriendResultFailureReason can be passed as a parameter. When we handle the response on the upper level, we can specify how we handle the response depending on where we call it. Some views might want to show an error popup while others might also want to move the user another view. For example to login view in case of unauthorized error. This approach is a bit different from what you find on the Alamofire GitHub readme-file. But this is a very useful approach in many cases.
Now if all went good and success block gets called the response result is cast to JSON array. We use guard here to see that it succeeds and call failure block if it didn’t work. FlatMap is used to create Friend objects from the array full of JSON objects. The data is set as a payload parameter in the success block. The reason we use flatMap is that Friends initializer might return nil. This way you don’t end up with nil objects in the array since flatMap does not allow Optionals. In case the JSON array is empty an empty array set as the payload parameter.
Great post. Waiting for the next parts!
Thanks Rob,
Second part is coming later this week.
Really great post, thanks a lot
Thanks a lot Amarok! I am glad you liked it 🙂
Hi, I am very glad to see this tutorial and i learn many this from this tutorial. its helped to learn MVVM pattern. Btw i did this project with Rx-swift , Here is the link :
“https://github.com/Morshed22/FriendZone”
Hope it helps anyone or Any one can recommend me to do better.
do you have idea about Graphql Apollo in swift?
Hi Karthik,
No I haven’t used it. But it seems that wenderlich has some post about it. You might want to check that one!
– Jimmy