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

Lets start by creating a new xCode project called Friends. Select single view application template and create the project for your chosen location. You can copy the folder structure presented in the image below. Or you can create it along the way while creating the new structs and classes for the project.
Friends MVVM project in Xcode

Friends project

The first task on the todo-list is to download all the friends stored on the server. We can do that with:
  1. AppServerClient that will download all the data from the server.
  2. Model to store the data into.
Before implementing the AppServerClient, we’ll first install a library called Alamofire.

 

Prepare CocoaPods & Alamofire

Alamofire helps with the HTTP-request and we can add it to the project using CocoaPods. In case you are not familiar with CocoaPods, I suggest that go and check it. You also get the installation guide from the previous link. So if you don’t have CocoaPods on your machine, install it and then come right back at this tutorial. We also need a Podfile which tells Cocoa pods what pods we want to install. Use a text editor and create a file named Podfile in your project root folder. Inside the Podfile put these lines:

I am not going to go in too much detail about Cocoa pods since it is not the subject of this post. You can check all the information on the Cocoa pods website. Make sure that target is actually the name of your project. If you created a project with a different name, you need to replace ‘Friends’ with your project name.  Between the project target and the ‘end’, you define all the pods that you want to install. Friend application is going to use Alamofire with version between 4.0-5.0 where 5.0 is not included. If you want to know more about the Podfile syntax you can read it here Podfile.
 
Now that the Podfile is set, close your Xcode project and open terminal in your project folder. Next, you need to run ‘pod install’:
pod install

pod install

If all goes well you should see the same output as in the image above. From now on always open your project using the .xcworkspace file as instructed. Inside the project folder, you see that ‘pod install’ created a folder named Pods. This is the place where it stores all the pod related stuff. The script also creates a “Podfile.lock” file. It contains the version information of all the pods installed in the latest pod install. Next open the project again using the Friends.xcworkspace file.

 

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:

The swift backend stores firstname, lastname and a phonenumber to the database. The database also contains id for all the friends and that information is also used in the client app. This is the data that we receive from the server. We don’t want to change it, so we define all the variables as a let to make it immutable. 
As you noticed we also use a type called JSON here. It is just a new name for a Dictionary. JSON is a lot easier and faster to write than Dictionary. So it is handy to use in projects that handles a lot of JSON. Create a new folder called “Helpers” and put a file named json.swift in it. Then define the JSON type alias like this:

Now concentrate on the initializer for a moment. With structs, it is good to define the initializer as an extension. By doing so we’ll get the benefits of the default initializer, provided by the framework. We’ll use the initializer defined here in the AppServer class. But we can still use the default initializer when we need to create MockFriends for unit tests. The init function goes through all the keywords and returns nil if some of it is not available. We don’t want our friends to lack any important information. So we need all the information available to create a friend.

 

There are a lot of good libraries which can do the JSON parsing for you, but sometimes it is a good idea to do it your self. It doesn’t take that long and you’ll have a bit more control over the data.

 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:

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:

  1. GetFriendsCompletion
  2. Result
  3. GetFriendsResult
  4. 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

Create a new file called Result.swift and put it inside “Helpers” folder. Result is an enum which has 2 cases: success & failure. Success case has payload parameter defined as T (Generic) and it means that it can have any information in it. For example, we could store friend information as an array of strings. Failure case takes parameter U (Generic) and it is defined as an Error. This means, whatever we put inside needs to conform to Error protocol.
 
In case you want a deeper understanding of the Result. You might want to check this great talk from Saul Mora. He explains the topic in more detail Result Oriented Programming.

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.

In the failure block, we fetch the underlying status code. Then we convert it to GetFriendsFailureReason using the enums default initializer. Then it is set as the error parameter in the failure block. In case there is no matching status code nil is set as a parameter.
 
Now that was quite a lot of text and code and the first part of this series is coming to an end. If you want you can examine the source codes on GitHub and find out more. If you are still full of energy you can also move to the second part of the series. In the second part, I’ll explain more about the view and view model part.
Thank you for reading and have a great day my friend!

This article has 7 comments

  1. Loser Reply

    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.

    • Jimmy Reply

      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

Leave a Comment

Your email address will not be published. Required fields are marked *