The Ultimate Course to Making Professional iOS Apps – Lesson 2

The Ultimate Course to Making Professional iOS Apps

Lesson 2: handling data and business logic through model types and model controllers

In the last lesson we have seen how the MVC pattern guides you in writing professionally written iOS apps. We have also seen how many developers don’t know that there are 4 layers in the MVC pattern instead of 3.

In this lesson we will go more deeply into the lowest layers of the MVC pattern and we will lay the foundation upon which we will build our app.

The roles of the model and model controllers

We will start laying the foundations of our app from the lowest layers of the MVC pattern. This is because these foundations are independent from the flow of the screens our app has. As such they don’t need to know anything about the upper layer.

Nonetheless a lot of what powers a well structured app lays in these lower layers. The biggest mistake developers make structuring iOS apps is to put most of this functionality in the upper layers instead.

Let’s start from the model layer. The first big mistake many make is to make this layer too thin. What I have seen very often is that developers use this layers only represent the data in their apps. But the model layer should take care of much more. The responsibilities of the model layer are:

  • representing the data of an app
  • encapsulating the business logic related to the domain of the app
  • taking care of transforming data from one format to another

Many developers put the last two elements of this list inside view controllers instead. In the same way model controllers also have responsibilities that developers usually put inside view controllers. In general, model controllers deal with the model, encapsulating functionality that I did not list above. These include:

  • keeping the app’s state
  • storing data using one of the many solutions offered by iOS
  • transmitting data over the network
  • dealing with data and events coming from the device sensors (GPS, accelerometer, gyroscope, cameras, etc)

Let’s have a look at how to implement these concepts in our app.

The model represents data and business logic

Let’s start representing the data of our app. Looking at the requirements we gathered in the previous lesson, there are two entities we need in our app: accounts and transactions. Let’s start from the latter. Every transaction needs:

  • an amount
  • a description
  • a date
  • a category

This translates straightforwardly into a struct:

We used a struct here because we want our data to be inert. Structures in Swift have value semantics, which means that when you pass the around you pass independent copies. This means we can safely pass our data around without the risk of having unexpected bugs.

Just so that you know, we will use Float values for money, but in any app that needs precise numbers, like money or scientific calculations, floating point numbers are not appropriate because of rounding errors.

For the category of a transaction I created a separated enum called TransactionCategory. This allows us to limit the possible values to only the six ones we allow in our app. We will see in a moment why this enum has a String raw value type.

We can proceed in a similar fashion for our accounts. An account needs:

  • a name
  • a bank name
  • an account number
  • a list of transactions
  • the total of the money in the account

This is the corresponding struct:

Notice one important thing here: total is calculated from the amount of all the transactions in the account. This is an example of how business logic belongs to the model. Many developers make the mistake to put this logic into controllers and use the model only for data.

I calculate total by adding the amount of a transaction with .income category and subtracting its amount in any other case. I did it in just one line using the reduce() function and the ternary operator, but it’s not really important how I did it. You could do it with a simple for loop and an if statement if you preferred. What is important is that this business logic belongs to the model and not to controllers, regardless of how big or small it is.

The model also takes care of data transformations

We are not finished with the model yet. We want to save our data on the disk using a very simple but effective iOS technology called Property Lists. Property Lists (or plists) in iOS store structured data in the form of simple types:

  • values are represented by strings, numbers, booleans, dates or binary data
  • objects are represented by dictionaries with keys of type String and values of the types listed above
  • lists of objects are represented by arrays of dictionaries

Property lists have the advantage that they can easily be saved to a file and read back with a single line of code. So, to store our accounts and transactions in the form of property lists, we need to transform them into dictionaries. Moreover, when we read them from a plist file, we need to convert them back into our structs.

This is again logic that most developers would put into a controller. But as I said above, transformation logic goes into the model instead. So, lets transform our transactions from Transaction into a dictionary and viceversa:

This code does not really do much, but again, the important part is that it belongs to the model and not to controllers. There are better, albeit more complicated, ways to write this code, but we are not interested in those at the moment.

We can do the same for our accounts:

Notice that the plistRepresentation property in this case relies on the plistRepresentation property of the Transaction struct. So when we transform an account into a dictionary, it will carry also all its transactions in a dictionary format as well.

Now that we have this infrastructure in place, we can move up to the model controllers layer.

Model controllers manage the model

As we have seen above, model controllers manage the model and deal with data storage and app state, among other things. So, while the transformation of data goes into the model, saving and reading such data goes into a model controller instead.

So we are going to write a model controller that takes care of the actual saving and reading of our data using plists files.

The first thing to notice here is that we used a class and not a struct. While we want our data to be inert, model controllers need to be the opposite. We need them to have reference semantics. In this way we can pass shared instances to other objects and make sure that they all access the same single central instance.

This object does not do much either. First, it has a couple of convenience properties. The documentsDirectoryURL property returns the URL of the documents directory (which is the directory in which you save the data of an iOS app). The accountsFileURL property uses documentsDirectoryURL to create the full URL of the file in which we will save the account. I could put them both in a single property, but I prefer to have small properties that do only one thing.

We actually need this file to save our accounts and nothing else. Recall that each account also includes its own transactions, so we will save everything at one time.

The core functionality of the StorageController class is in its two methods. The save(_:) method stores the account in a plist file, using the write(to:atomically:) method of the NSArray class. Notice that it does so using the plistRepresentation property to first transform every Account struct into a plist. The fetchAccounts() method does the opposite. It retrieves the saved accounts from the file and converts them back to Account structs, using the init(plist:) initializer of Account.

For our small app this model controller could be enough. We could use it to just read and save data every time we make a change. But in a real world app, things would rarely be this simple. A real app needs to keep its own state in memory and share it around to all objects that need it. Saving and reading instead happens only at specific times because it is more expensinve than data that is already in memory. So we will need a separate model controller to keep such data in memory. Always keep responsibilities separate between classes. If a class already deals with one task, like storage in our example, it should not have more responsibility like keeping state. For that we create a StateController class:

This class keeps the shared state of the app. In our simple example it does so by keeping the list of accounts inside an array.

There are two important considerations to keep in mind here.

For our model we are using Swifts structs, which have value semantics. These have the advantage of reducing bugs due to unexpected changes to copies of our data around the app. The disadvantage though is that these structs cannot alone keep the state of the app. We need a central place where a consistent state of our data is kept. This place is our StateController, which is a class instead.

Every time we create a new account we need to add it to the accounts array inside of StateController. In the same way, when we create a new transaction we have to add it to the corresponding account in this array. Simply adding a new transaction to an Account struct somewhere else in the app would not affect the central values in this array. That is why our StateController has both a method to add a new account and a method to update an existing account. The latter looks for the old copy in the array and replaces it with a new one.

The second consideration is that the StateController keeps a reference to an instance of StorageController and uses it to save and read data from the disk. For simplicity it does so every time we add or update a new account. This is not typical because, as I said, accessing files can be very expensive. In a normal app we would not save or read data every time we make a change to the state. We would probably separate saving and reading into separate methods that we could call only when we launch or close the app. But here I want to keep things simple. The important part is the overarching concept of model controllers, not these details.

It is important to understand that model controllers often need not only to interact to the model. They often need references to other model controller as well.

The model controller layer of our app is finished and we can move up to the other layers of the MVC pattern.

Summary

In the lessons of this mini course we are moving fast and glossing over details. There is no time or space here to go over them, since what I want to give you here is an overview and understanding of how professional apps are structured. I will share with you more free material on the various aspects of iOS development after this mini course ends.

What is important to stress in this lesson is:

  • the model layer does not only represent data. It also encapsulates business logic and performs transformations between different data formats
  • model controllers deal with different aspect related to the model, like keeping state or storing data among others
  • model controllers deal not only with model data, but also with other model controllers. Together they are at the core of the functionality of the whole app

If you want to go more deeply into the all the details, my “The Confident iOS Professional” Course does so. It took me two years to complete it and to make sure I included every important aspect of developing complete iOS apps. The course opens regularly only to subscribers of my list. So if you are interested stay in my list and you will be notified when it opens.

Here is a preview of the modules in the course related to what we saw in this lesson:

Module 1 – Architectue: The Fundamental Concept Nobody Talks About.

  • The most important software development practices and the bad consequences of not following them. These are the concepts that drive good software development. Developers that just copy and paste code from tutorials never understand these
  • The three fundamental architectural concepts that guide the development of every app. Ignore them and you will produce unmanageable code that will soon bite you back

Module 2 – The Core of Every iOS App: The Model View Controller Pattern

  • Why design patterns are crucial to well designed software and how they will help you write more robust apps
  • The MVC pattern, the central and most important design pattern of iOS. Ignore this and you will constantly fight against the platform and introduce bugs that are hard to find
  • How the boundaries of MVC are actually blurred and which objects sit at these boundaries. Many developers that don’t understand this concept think they are following the MVC pattern correctly, when they actually are not

Master Module 1: Persisting Data – The Different Ways to Save Data in an iOS App

  • The correct technologies you should chose when persisting data in any app you make. Many developers pick the wrong solution for their app and do a lot of unnecessary work.
  • How to easily and safely save and read data in your app, without having to learn to use complicated solutions that are too complex for you app.
  • The correct architectural patterns to use for the storage code of your app, avoiding the common pitfalls that cause problems to a lot of apps. This is an important part that many developers get wrong.

Master Module 2: Connecting to the Network – Connecting to a Remote Web API and Making Requests in the Background

  • How code is executed concurrently in iOS. You need to know this important concept to understand what task might make your app unresponsive.
  • The internet protocols used to create REST APIs. You need to know how these fundamental internet technologies work to connect your app to a remote API and to troubleshoot any problems you come across
  • How to model the resources of a remote REST API and where exactly to put your networking code. Many developers get this wrong and produce garbled code that is hard to test and extend.