Beginning iOS 16 Programming with Swift and UIKit

Chapter 11
Object Oriented Programming, Project Organization and Code Documentation

Most good programmers do programming not because they expect to get paid or adulation by the public, but because it is fun to program.

- Linus Torvalds

If you read from the very beginning of the book and have worked through all the projects, you've gone pretty far. By now, you should be able to build a table-based iOS app, which works on both iPhone and iPad, using Interface Builder. We'll further enhance the FoodPin app and add more features. However, we will dive deeper into iOS app development and learn other APIs. I want to introduce you the basics of Object Oriented Programming and teach you how to write better code.

Don't be scared by the term "Object Oriented Programming" or OOP in short. It's not a new kind of programming language, but a programming concept. While some programming books start out by introducing the OOP concept, I intentionally left it out when I began writing this book. I want to keep things interesting and show you how to create an app. I don't want to scare you away from building apps, just because of a technical term or concept. Having said that, I think it's time to discuss OOP. After going through 10 chapters and you're still reading the book, I believe you're determined to learn iOS programming. And, I believe you really want to take your programming skills to the next level to become a professional developer.

Okay, let's get started.

The Basic Theory of Object Oriented Programming

Like Objective-C, Swift is known as an Object Oriented Programming (OOP) language. OOP is a way of constructing software application composed of objects. In other words, the code written in an app in some ways deals with objects of some kinds. The UIViewController, UIButton, UINavigationController, and UITableView objects that you have used are some sample objects that come with the iOS SDK. Not only can you use the built-in objects, you have already created your own objects in the project, such as RestaurantTableViewController and RestaurantTableViewCell.

First, why OOP? One important reason is that we want to decompose complex software into smaller pieces (or building block) which are easier to develop and manage. Here, the smaller pieces are the objects. Each object has its own responsibility and objects coordinate with each other in order to make the software work. That is the basic concept of OOP.  Take the Hello World app, that we've built at the very beginning, as an example. The UIViewController object is responsible for controlling the view of the app and its view object is used for holding the Hello World button. The UIButton (i.e. Hello World button) object implements a standard iOS button on the touch screen and listens to any touch events. On the other hand, the UIAlertController object displays an alert message to a user. After all, these objects work together to create the Hello World app.

Figure 11-1. Sample Objects in Hello World App
Figure 11-1. Sample Objects in Hello World App

In Object Oriented Programming, an object shares two characteristics: properties and functionalities. Let's consider a real world object – Car. A car has its own color, model, top speed, manufacturer, etc. These are the properties of a car. In terms of functionalities, a car should provide basic functions, such as accelerate, brake, steering, etc.

Software objects are conceptually similar to real-world objects. If we go back to the iOS world, let's take a look at the properties and functionalities of the UIButton object in the Hello World app:

  • Properties – Background, size, color, and font are examples of the UIButton's properties
  • Functionalities – When the button is tapped, it recognizes the tap event. The ability to detect a touch is one of the many functions of UIButton.

In earlier chapters, you always come across the term method. In Swift, we create methods to provide the functionalities of an object. Usually, a method corresponds to a particular function of an object.

Note: You may wonder what is the difference between functions and methods? In fact, they are functionally the same. Functions defined in a class are known as methods.

Classes, Objects and Instances

Other than methods and objects, you have come across terms like instance and class . All these are the common terms in OOP. Let me give you a brief explanation.

A class is a blueprint or prototype from which objects are created. Basically, a class consists of properties and methods. Let's say, we want to define a Course class. A Course class contains properties, such as name, course code, and the total number of students.

This class represents the blueprint of a course. We can use it to create different courses like iOS Programming course with course code IPC101, Cooking course with course code CC101, etc. Here, the "iOS Programming course" and "Cooking course" are known as the objects of the Course class. We typically refer a single course as an instance of the Course class. For the sake of simplicity, the term instance and object are sometimes interchangeable.

A blueprint for a house design is like a class description. All the houses built from that blueprint are objects of that class. A given house is an instance.

Source: http://stackoverflow.com/questions/3323330/difference-between-object-and-instance

Structures

Structures and classes are general-purpose, flexible constructs that become the building blocks of your program’s code. You define properties and methods to add functionality to your structures and classes using the same syntax you use to define constants, variables, and functions.

- Apple Documentation (https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html)

Other than classes, you can use use structures (or structs) in Swift to create your own type with properties and methods. Structs in Swift are very similar to Classes and have a lot in common. Both can define properties to store values and methods to provide functionalities. Both can create their own initializers to configure the initial state of the object.

Inheritance is one of the important features of Object Oriented Programming. Recalled that we have created the RestaurantTableViewController class, which is a subclass of the UITableViewController, this is a sample usage of class inheritance. The RestaurantTableViewController class inherits all the properties and methods of UITableViewController. Whenever we need to provide specialization for a particular function, we override the original function of the super class.

Structs, however, do not allow inheritance. You can't inherit one struct from another. This is one of main differences between Classes and Structs in Swift.

Types in Swift fall into two categories: value types and reference types. All structures in Swift are value types, while classes are reference types. This is another core difference between Classes and Structs. For structs, each instance has a unique copy of its data. On the other hand, reference types (classes) share a single copy of the data. When you assign an instance of a class to another variable, rather than copy the data of that instance, a reference to that instance is used.

Let me demonstrate the difference between value types (struct) and reference types (class) using an example. In the code snippet below, we have a class named Car with a property named brand. We create an instance of Car and assign it to a car1 variable. Then we assign car1 to another variable named car2. Lastly, we modify the brand value of car1.

class Car {
    var brand = "Tesla"
}

var car1 = Car()
var car2 = car1

car1.brand = "Audi"
print(car2.brand)

Can you guess the brand value of car2? Should it be Tesla or Audi? The answer is Audi. This is the nature of reference types. Both car1 and car2 are referring to the same instance, sharing the same copy of the data.

Contrarily, if you rewrite the same piece of code with struct (i.e. value types), you will see a different result.

struct Car {
    var brand = "Tesla"
}

var car1 = Car()
var car2 = car1

car1.brand = "Audi"
print(car2.brand)

In this case, only the brand value of car1 is updated to Audi. For car2, its brand is still Tesla because each variable of value types has its own copy of data. Figure 11-2 illustrates the difference between classes and structs visually.

Figure 11-2. Illustrates the difference between value types and reference types
Figure 11-2. Illustrates the difference between value types and reference types

Since both classes and strutures provides similar features, which one should you use? In general, use structures to create your own type by default. This is a recommendation from Apple (https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes). In case if you need additional functionalities like inheritance, choose classes over structs.

Revisit the FoodPin Project

So, why do we cover OOP in this chapter? There is no better way to explain the concept than showing you an example. Take a look at the FoodPin project (http://www.appcoda.com/resources/swift57/FoodPinTableSelectionExercise.zip) again.

In the RestaurantTableViewController class, we created multiple arrays to store the names, types, locations, and images of the restaurants.

var restaurantNames = ["Cafe Deadend", "Homei", "Teakha", "Cafe Loisl", "Petite Oyster", "For Kee Restaurant", "Po's Atelier", "Bourke Street Bakery", "Haigh's Chocolate", "Palomino Espresso", "Upstate", "Traif", "Graham Avenue Meats", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "CASK Pub and Kitchen"]

var restaurantImages = ["cafedeadend", "homei", "teakha", "cafeloisl", "petiteoyster", "forkeerestaurant", "posatelier", "bourkestreetbakery", "haighschocolate", "palominoespresso", "upstate", "traif", "grahamavenuemeats", "wafflewolf", "fiveleaves", "cafelore", "confessional", "barrafina", "donostia", "royaloak", "caskpubkitchen"]

var restaurantLocations = ["Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Sydney", "Sydney", "Sydney", "New York", "New York", "New York", "New York", "New York", "New York", "New York", "London", "London", "London", "London"]

var restaurantTypes = ["Coffee & Tea Shop", "Cafe", "Tea House", "Austrian / Causual Drink", "French", "Bakery", "Bakery", "Chocolate", "Cafe", "American / Seafood", "American", "American", "Breakfast & Brunch", "Coffee & Tea", "Coffee & Tea", "Latin American", "Spanish", "Spanish", "Spanish", "British", "Thai"]

var restaurantIsFavorites = Array(repeating: false, count: 21)

All these data are actually related to a list of restaurants. Why do we need to separate them into multiple arrays? Have you ever wondered if we can group these data together?

In Object Oriented Programming, these data can be characterized as the properties of a restaurant. Instead of storing these data in separate arrays, we can create a Restaurant structure to model a restaurant and store multiple restaurants in an array of Restaurant objects.  Figure 11-3. Combining multiple arrays into an array of Restaurant objects

Now let's tweak the FoodPin project, create the Restaurant struct and convert the code to use a list of Restaurant objects.

Creating a Restaurant Struct

First, we'll start with the Restaurant struct. In the project navigator, right click on the FoodPin folder and select "New File…". This time we are not extending the UI objects provided by the iOS SDK. Instead, we are going to create a brand new struct. So, choose the "Swift File" template under Source and click "Next". Name the file Restaurant.swift and save it in the project folder.

Figure 11-4. Creating a new class using the Swift File template
Figure 11-4. Creating a new class using the Swift File template

Once completed, declare the Restaurant struct in the Restaurant.swift file using the below code:

struct Restaurant {
    var name: String
    var type: String
    var location: String
    var image: String
    var isFavorite: Bool

    init(name: String, type: String, location: String, image: String, isFavorite: Bool) {
        self.name = name
        self.type = type
        self.location = location
        self.image = image
        self.isFavorite = isFavorite
    }

    init() {
        self.init(name: "", type: "", location: "", image: "", isFavorite: false)
    }
}

You use the struct keyword to define a structure. The code above defines a Restaurant struct with five properties including name, type, location, image and isFavorite. Except for isFavorite, which is a boolean (i.e. Bool), the rest of the properties are of the type String. You can either set a default value or explicitly specify the type for each property. Here we demonstrate the latter option.

Initializers Explained

Initialization is the process of preparing an instance of a structure (or class). When you create an object, the initializer is called for setting an initial value for each stored property on that instance and performing any extra setup, before the instance is ready to use. You use the init keyword to define an initializer. In its simplest form, it looks like this:

init() {

}

You can also customize an initializer to take input parameters, just like the one we have defined in the Restaurant struct. Our initializer has five parameters. Each of them is given a name and explicitly specified with a type. In the initializer, it initializes the values of the property with the given values.

To create an instance of the Restaurant struct, the syntax is like this:

Restaurant(name: "Thai Cafe", type: "Thai", location: "London", image: "thaicafe", isFavorite: false)

You are allowed to define multiple initializers that accepts different parameters. In the code, we created another initializer for convenience purpose.

init() {
    self.init(name: "", type: "", location: "", image: "", isFavorite: false)
}

Without this initializer, you can initialize an empty Restaurant object like this:

Restaurant(name: "", type: "", location: "", image: "", isFavorite: false)

Now with the convenience initializer, you can initialize the same object like this:

Restaurant()

It saves you time from typing all the initialization parameters every time you need to initialize an empty Restaurant object.

The self Keyword

What's the self keyword in the initializer? In Swift, you use self to differentiate between property names and arguments in initializers. Because the arguments have the same name as the properties, we use self to refer to the property of the class.

Figure 11-5. The use of self keyword
Figure 11-5. The use of self keyword

Default Initializers

You can actually assign each property with a default value and omit the initializator. Swift will generate the default initalizer behind the scene. Therefore, a short version of the Restaurant struct can be written like this:

struct Restaurant {
    var name: String = ""
    var type: String = ""
    var location: String = ""
    var image: String = ""
    var isFavorite: Bool = false
}

Using the Array of Restaurant Objects

With a basic understanding of classes, structs, and object initializations, let's go back to the FoodPin project and combine the existing arrays into an array of Restaurant objects. First, delete the restaurant-related arrays from the RestaurantTableViewController class:

var restaurantNames = ["Cafe Deadend", "Homei", "Teakha", "Cafe Loisl", "Petite Oyster", "For Kee Restaurant", "Po's Atelier", "Bourke Street Bakery", "Haigh's Chocolate", "Palomino Espresso", "Upstate", "Traif", "Graham Avenue Meats", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "CASK Pub and Kitchen"]

var restaurantImages = ["cafedeadend", "homei", "teakha", "cafeloisl", "petiteoyster", "forkee", "posatelier", "bourkestreetbakery", "haigh", "palomino", "upstate", "traif", "graham", "waffleandwolf", "fiveleaves", "cafelore", "confessional", "barrafina", "donostia", "royaloak", "cask"]

var restaurantLocations = ["Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Sydney", "Sydney", "Sydney", "New York", "New York", "New York", "New York", "New York", "New York", "New York", "London", "London", "London", "London"]

var restaurantTypes = ["Coffee & Tea Shop", "Cafe", "Tea House", "Austrian / Causual Drink", "French", "Bakery", "Bakery", "Chocolate", "Cafe", "American / Seafood", "American", "American", "Breakfast & Brunch", "Coffee & Tea", "Coffee & Tea", "Latin American", "Spanish", "Spanish", "Spanish", "British", "Thai"]

var restaurantIsFavorites = Array(repeating: false, count: 21)

Instead of using the above arrays, replace them with a new array of Restaurant objects:

var restaurants:[Restaurant] = [
    Restaurant(name: "Cafe Deadend", type: "Coffee & Tea Shop", location: "Hong Kong", image: "cafedeadend", isFavorite: false),
    Restaurant(name: "Homei", type: "Cafe", location: "Hong Kong", image: "homei", isFavorite: false),
    Restaurant(name: "Teakha", type: "Tea House", location: "Hong Kong", image: "teakha", isFavorite: false),
    Restaurant(name: "Cafe loisl", type: "Austrian / Causual Drink", location: "Hong Kong", image: "cafeloisl", isFavorite: false),
    Restaurant(name: "Petite Oyster", type: "French", location: "Hong Kong", image: "petiteoyster", isFavorite: false),
    Restaurant(name: "For Kee Restaurant", type: "Bakery", location: "Hong Kong", image: "forkee", isFavorite: false),
    Restaurant(name: "Po's Atelier", type: "Bakery", location: "Hong Kong", image: "posatelier", isFavorite: false),
    Restaurant(name: "Bourke Street Backery", type: "Chocolate", location: "Sydney", image: "bourkestreetbakery", isFavorite: false),
    Restaurant(name: "Haigh's Chocolate", type: "Cafe", location: "Sydney", image: "haigh", isFavorite: false),
    Restaurant(name: "Palomino Espresso", type: "American / Seafood", location: "Sydney", image: "palomino", isFavorite: false),
    Restaurant(name: "Upstate", type: "American", location: "New York", image: "upstate", isFavorite: false),
    Restaurant(name: "Traif", type: "American", location: "New York", image: "traif", isFavorite: false),
    Restaurant(name: "Graham Avenue Meats", type: "Breakfast & Brunch", location: "New York", image: "graham", isFavorite: false),
    Restaurant(name: "Waffle & Wolf", type: "Coffee & Tea", location: "New York", image: "waffleandwolf", isFavorite: false),
    Restaurant(name: "Five Leaves", type: "Coffee & Tea", location: "New York", image: "fiveleaves", isFavorite: false),
    Restaurant(name: "Cafe Lore", type: "Latin American", location: "New York", image: "cafelore", isFavorite: false),
    Restaurant(name: "Confessional", type: "Spanish", location: "New York", image: "confessional", isFavorite: false),
    Restaurant(name: "Barrafina", type: "Spanish", location: "London", image: "barrafina", isFavorite: false),
    Restaurant(name: "Donostia", type: "Spanish", location: "London", image: "donostia", isFavorite: false),
    Restaurant(name: "Royal Oak", type: "British", location: "London", image: "royaloak", isFavorite: false),
    Restaurant(name: "CASK Pub and Kitchen", type: "Thai", location: "London", image: "cask", isFavorite: false)
]

Once you replaced the original arrays with the restaurants array, you'll end up with a few errors in Xcode because some of the code still refer to the old arrays.

Figure 11-6. You can click the cross indicator to reveal the errors
Figure 11-6. You can click the cross indicator to reveal the errors

To fix the errors, we have to modify the code to use the new restaurants array. First, update the configureDataSource() method like this:

func configureDataSource() -> UITableViewDiffableDataSource<Section, Restaurant> {

    let cellIdentifier = "datacell"

    let dataSource = UITableViewDiffableDataSource<Section, Restaurant>(
        tableView: tableView,
        cellProvider: {  tableView, indexPath, restaurant in
            let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RestaurantTableViewCell

            cell.nameLabel.text = restaurant.name
            cell.locationLabel.text = restaurant.location
            cell.typeLabel.text = restaurant.type
            cell.thumbnailImageView.image = UIImage(named: restaurant.image)
            cell.favoriteImageView.isHidden = restaurant.isFavorite ? false : true

            return cell
        }
    )

    return dataSource
}

Since we now use an array of Restaurant object instead of an array of restaurant names to model the restaurants, the item type of UITableViewDiffableDataSource is now changed to Restaurant. For the cellProvider closure, it now passes us a Restaurant object instead of a String. So, we can retrieve the restaurant data by accessing the properties of the Restaurant object.

Next, replace these lines of code in viewDidLoad():

var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.all])
snapshot.appendItems(restaurantNames, toSection: .all)

With:

var snapshot = NSDiffableDataSourceSnapshot<Section, Restaurant>()
snapshot.appendSections([.all])
snapshot.appendItems(restaurants, toSection: .all)

Again, the item type of the diffable data source is now changed from String to Restaurant. Instead of appending the restaurantNames to the section, we add the items in the restaurants array.

Other than that, you should see a number of errors in the tableView(_:didSelectRowAt:) method since the restaurantIsFavorites array is no longer available. You can replace the code of favoriteActionTitle and favoriteAction like below:

let favoriteActionTitle = self.restaurants[indexPath.row].isFavorite ? "Remove from favorites" : "Mark as favorite"
let favoriteAction = UIAlertAction(title: favoriteActionTitle, style: .default, handler: {
    (action:UIAlertAction!) -> Void in

    let cell = tableView.cellForRow(at: indexPath) as! RestaurantTableViewCell

    cell.favoriteImageView.isHidden = self.restaurants[indexPath.row].isFavorite

    self.restaurants[indexPath.row].isFavorite = self.restaurants[indexPath.row].isFavorite ? false : true

})

We now access the isFavorite property of the selected restaurant objects to check its value.

Do we fix all the errors? Not yet. If you scroll back to the configureDataSource() method and the viewDidLoad() method, you should see a bunch of errors, complaining the type Restaurant.

Figure 11-7. Error message for the Restaurant type
Figure 11-7. Error message for the Restaurant type

What's the problem of the Restaurant struct? What does the error message mean? The UITableViewDiffableDataSource class has two generic types, one for the section identifier and one for the item. For both types, it is required to conform to the Hashable protocol. Since the Restaurant type doesn't conform to Hashable, Xcode shows the error.

You may wonder why this error only shows up for this newly created Restaurant type. We didn't see this kind of error when using the String type. The reason is that the Swift standard types, such as String and enum, already conform to Hashable. For the Restaurant struct, however, we have to adopt the Hashable manually.

You do not need to write extra code to implement the protocol. All you need to do is declare the conformance like this:

struct Restaurant: Hashable {
    var name: String = ""
    var type: String = ""
    var location: String = ""
    var image: String = ""
    var isFavorite: Bool = false
}

This is a built-in feature of Swift. Since all the properties of the Restaurant struct are Hashable, the compiler will automatically generate the required code for adopting the Hashable protocol.

This should fix all the errors. You can now run your app. The look & feel of the app is exactly the same as before. However, we have refactored the code to use the new Restaurant struct. By combining multiple arrays into one, the code is now cleaner and more readable.

Organizing Your Xcode Project Files

As we continue to build our app, we will create even more files in the project folder. Therefore, I want to take this chance to show you a technique to better organize our projects.

Let's first look at the project navigator. All files you have created are placed at the top level of the FoodPin folder. As you add more files, it will be getting more difficult to locate your desired file. To better organize your project files, Xcode provides a group feature that lets you organize the files by group/folder.

There are a number of ways to group your files. You may group them by features or functions. One good practice is to group them by their responsibilities in MVC (Model-View-Controller). Say, for view controllers, they will be grouped under Controller. Model classes like Restaurant will be grouped under Model. For custom views like RestaurantTableViewCells, they will be put under a group named View.

To create a group in the project navigator, right click the FoodPin folder and select New Group to create a new group. Name it Controller.

Figure 11-8. Right click FoodPin to create a new group
Figure 11-8. Right click FoodPin to create a new group

Next, select RestaurantTableViewController.swift and drag it to the Controller group.

Repeat the same procedure to group other files like that shown in figure 11-9 (left). If you open your project folder in Finder, you will find that all files are nicely organized into folders (see figure 11-9 (right)). Each of which corresponds to the specific group in your Xcode project.

Figure 11-9. Organizing project files into groups (left), Each group is a subfolder of the FoodPin folder (right)
Figure 11-9. Organizing project files into groups (left), Each group is a subfolder of the FoodPin folder (right)

Even if you have moved the files to different folders, you can still run the projects without any changes. Hit the Run button and try it out.

Documenting and Organizing Swift Code with MARK

"Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. ...[Therefore,] making it easy to read makes it easier to write." ― Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

Other than project files, there are some best practices to organizing the source code better. Here I am going to show you a powerful technique to organize your Swift code into useful and easy to read sections.

As you know, those lines of code which begins with // are comments. Comments serve as a note to yourself or other developers (if you are working in a team), providing extra information (e.g. intent) about the code. The primary purpose is just to make your code more easy to understand.

// Add "Reserve a table" action

You may have noticed that some comments have the // MARK: marker. Here is an example:

// MARK: - UITableViewDelegate Protocol

MARK is a special comment marker in Swift for organizing your code into easy-to-navigate sections. Say, for the RestaurantTableViewController class, some of the methods are related to the UITableViewDelegate protocol. And, some are related to view controller life cycle or the data source. We can then add the MARK comment to separate them into sections.

// MARK: - View controller life cycle

override func viewDidLoad() {

    .
    .
    .
}

// MARK: - UITableView Diffable Data Source

func configureDataSource() -> UITableViewDiffableDataSource<Section, Restaurant> {

    .
    .
    .
}

// MARK: - UITableViewDelegate Protocol

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    .
    .
    .
}

Now when you click the jump bar on top of your editor window, you can see the methods are organized into different meaningful sections.

Figure 11-10. Organizing your Swift code with MARK
Figure 11-10. Organizing your Swift code with MARK

Summary

Congratulations if you made it this far. I hope you're not bored by the chapter. What I have covered is the basics of Object Oriented Programming. I also showed you some techniques for organizing your code and project files.

There are a lot more about the OOP concepts, such as polymorphism. However, we do not have time to discuss in-depth in this book. If you want to become a professional iOS developer, check out the references to learn more. It'll take you a lot of practices to truly pick up OOP. Anyhow, if you manage to finish this chapter, this is a good start.

For reference, you can download the complete Xcode project from http://www.appcoda.com/resources/swift57/FoodPinOOP.zip. In the next chapter, base on what we've learned, you'll continue to tweak the detail view screen of the FoodPin app. It's going to be fun!

Further References

Swift Programming Language - Classes and Structures https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html

Swift Programming Language - Initialization https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

Swift Programming Language - Inheritance https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html

Object Oriented Programming from MIT Open Courseware https://ocw.mit.edu/courses/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/resources/lecture-8-object-oriented-programming/