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. ViewController tends to act both as View and a Controller. You end up putting all the View updating stuff and the logic in the ViewController. 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 ViewModel and ViewModel has a reference to Model and that’s it. Data state changes between View and ViewModel are handled with Data Binding pattern. The aim is to keep View as light as possible. ViewModel 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 View is so simple that you don’t need to write any tests for it. All you need to do is write tests for ViewModel. It’s easy to test different data states of the View by creating a mock data for ViewModel. Since there is no reference to UI-layer it is a lot easier to test ViewModel compared to View.

This tutorial will go through the best practises 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 setup 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 Model part of the MVVM-pattern. It also covers networking with proper error handling. The second part concentrates more in the ViewModel and the View of MVVM. It also cover 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 it 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 CocoaPods 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 CocoaPods since it is not the subject of this post. You can check all the information in the CocoaPods 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 now 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 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 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 server. We don’t want to change it, so 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 Dictionary. JSON is a lot easier and faster to write than “Dictionary<String, Any>”. So it is handy to use in projects that handles lot of JSON. Create a new folder called “Helpers” and put a file named json.swift in it. Then define the JSON typealias like this:

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

 

There is a lot 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 on the data.

 AppServerClient with Alamofire

AppServerClient is not actually part of the MVVM pattern. But I wanted 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 @escaping function which means it has 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 a 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, what ever we put inside needs to conform to Error protocol.
 
In case you want deeper understanding on 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 itas Int, it 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. It is also pretty much a standard way of handling networking in iOS. AppServerClient is not a complete example of Alamofire networking. For example, this version uses default SessionManager and http-headers. You might want to setup those your self in 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 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 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 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.

Hanlding the response

In the completion block use switch to get the result of the DataResponse. 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 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 unauthorised error. This approach is a bit different from what you find on the Alamofire github readme-file. But this is 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 payload parameter in the success block. The reason we use flatMap is that Friends initialiser 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 initialiser. Then it is set as the error parameter in the failure block. Incase 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 till full of energy you can also move to the second part of the series. In the second part I’ll explain more about View and ViewModel part.
Thank you for reading and have a great day my friend!

This article has 5 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.

Leave a Comment

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