Swift From Scratch: An Introduction to Classes and Structures

Up to now, we've covered the basics of the Swift programming language. If you followed along, you should now have a solid understanding of variables, constants, functions, and closures. It's now time to use what we've learned and apply that knowledge to the object-oriented structures of Swift.

To understand the concepts discussed in this tutorial, it's important that you have a basic understanding of object-oriented programming. If you're not familiar with classes, objects, and methods, then I recommend you first read up on these topics before continuing with this tutorial.

eBook: Object-Oriented Programming With Swift

1. Introduction

In this lesson, we're going to explore the fundamental building blocks of object-oriented programming in Swift, classes and structures. In Swift, classes and structures feel and behave very similarly, but there are a number of key differences that you need to understand to avoid common pitfalls.

In Objective-C, classes and structures are very different. This isn't true for Swift. In Swift, for example, both classes and structures can have properties and methods. Unlike C structures, structures in Swift can be extended, and they can also conform to protocols.

The obvious question is: "What's the difference between classes and structures?" We'll revisit this question later in this tutorial. Let's first explore what a class looks like in Swift.

2. Terminology

Before we start working with classes and structures, I'd like to clarify a few commonly used terms in object-oriented programming. The terms classes, objects, and instances often confuse people new to object-oriented programming. Therefore, it's important that you know how Swift uses these terms.

Objects and Instances

A class is a blueprint or template for an instance of that class. The term "object" is often used to refer to an instance of a class. In Swift, however, classes and structures are very similar, and therefore it's easier and less confusing to use the term "instance" for both classes and structures.

Methods and Functions

Earlier in this series, we worked with functions. In the context of classes and structures, we usually refer to functions as methods. In other words, methods are functions that belong to a particular class or structure. In the context of classes and structures, you can use both terms interchangeably since every method is a function.

3. Defining a Class

Let's get our feet wet and define a class. Fire up Xcode and create a new playground. Remove the contents of the playground and add the following class definition.

The class keyword indicates that we're defining a class named Person. The implementation of the class is wrapped in a pair of curly braces. Even though the Person class isn't very useful in its current form, it's a proper, functional Swift class.

Properties

As in most other object-oriented programming languages, a class can have properties and methods. In the updated example below, we define three properties:

  • firstName, a variable property of type String?
  • lastName, a variable property of type String?
  • birthPlace: a constant property of type String

As the example illustrates, defining properties in a class definition is similar to defining regular variables and constants. We use the var keyword to define a variable property and the let keyword to define a constant property.

The above properties are also known as stored properties. Later in this series, we learn about computed properties. As the name implies, stored properties are properties that are stored by the class instance. They are similar to properties in Objective-C.

It's important to note that every stored property needs to have a value after initialization or be defined as an optional type. In the above example, we give the birthPlace property an initial value of "Belgium". This tells Swift that the birthplace property is of type String. Later in this article, we take a look at initialization in more detail and explore how it ties in with initializing properties.

Even though we defined the birthPlace property as a constant, it is possible to change its value during the initialization of a Person instance. Once the instance has been initialized, the birthPlace property can no longer be modified since we defined the property as a constant property with the let keyword.

Methods

We can add behavior or functionality to a class through functions or methods. In many programming languages, method is used instead of function in the context of classes and instances. Defining a method is almost identical to defining a function. In the next example, we define the fullName() method in the Person class.

The method fullName() is nested in the class definition. It accepts no parameters and returns a String. The implementation of the fullName() method is straightforward. Through optional binding, which we discussed earlier in this series, we access the values stored in the firstName and lastName properties.

We store the first and last name of the Person instance in an array and join the parts with a space. The reason for this somewhat awkward implementation should be obvious: the first and last name can be blank, which is why both properties are of type String?.

Instantiation

We've defined a class with a few properties and a method. How do we create an instance of the Person class? If you're familiar with Objective-C, then you're going to love the conciseness of the following snippet.

Instantiating an instance of a class is very similar to invoking a function. To create an instance, the name of the class is followed by a pair of parentheses, and the return value is assigned to a constant or variable.

In our example, the constant john now points to an instance of the Person class. Does this mean that we can't change any of its properties? The next example answers this question.

We can access the properties of an instance using the convenience of the dot syntax. In the example, we set firstName to "John", lastName to "Doe", and birthPlace to "France". Before we draw any conclusions based on the above example, we need to check for any errors in the playground.

Cannot Assign to Property

Setting firstName and lastName doesn't seem to cause any problems. But assigning "France" to the birthPlace property results in an error. The explanation is simple.

Even though john is declared as a constant, that doesn't prevent us from modifying the Person instance. There's one caveat, though: only variable properties can be modified after initializing an instance. Properties that are defined as constant cannot be modified once a value is assigned.

A constant property can be modified during the initialization of an instance. While the birthPlace property cannot be changed once a Person instance is created, the class wouldn't be very useful if we could only instantiate Person instances with a birthplace of "Belgium". Let's make the Person class a bit more flexible.

Initialization

Initialization is a phase in the lifetime of an instance of a class or structure. During initialization, we prepare the instance for use by populating its properties with initial values. The initialization of an instance can be customized by implementing an initializer, a special type of method. Let's define an initializer for the Person class.

We've defined an initializer, but we run into several problems. Take a look at the error the compiler throws at us.

Defining an Initializer

Not only is the initializer pointless, the compiler also warns us that we cannot modify the value of the birthPlace property since it already has an initial value. We can resolve the error by removing the initial value of the birthPlace property.

Note that the name of the initializer, init(), isn't preceded by the func keyword. In contrast to initializers in Objective-C, an initializer in Swift doesn't return the instance that's being initialized.

Another important detail is how we set the birthPlace property with an initial value. We set the birthPlace property by using the property name, but it's also fine to be more explicit like this.

The self keyword refers to the instance that's being initialized. This means that self.birthPlace refers to the birthPlace property of the instance. We can omit self, as in the first example, because there's no confusion about which property we're referring to. This isn't always the case, though. Let me explain what I mean.

Parameters

The initializer we defined isn't very useful at the moment, and it doesn't solve the problem we started with: being able to define the birthPlace of a person during initialization. 

In many situations, you want to pass initial values to the initializer to customize the instance you're instantiating. This is possible by creating a custom initializer that accepts one or more arguments. In the following example, we create a custom initializer that accepts one argument, birthPlace, of type String.

Two things are worth pointing out. First, we are required to access the birthPlace property through self.birthPlace to avoid ambiguity since the local parameter name is equal to birthPlace. Second, even though we haven't specified an external parameter name, Swift by default creates an external parameter name that's equal to the local parameter name.

In the following example, we instantiate another Person instance by invoking the custom initializer we just defined.

By passing a value for the birthPlace parameter to the initializer, we can assign a custom value to the constant birthPlace property during initialization.

Multiple Initializers

As in Objective-C, a class or structure can have multiple initializers. In the following example, we create two Person instances. In the first line, we use the default initializer. In the second line, we use the custom initializer we defined earlier.

4. Defining a Structure

Structures are surprisingly similar to classes, but there are a few key differences. Let's start by defining a basic structure.

At first glance, the only difference is the use of the struct keyword instead of the class keyword. The example also shows us an alternative approach to supply initial values to properties. Instead of setting an initial value for each property, we can give properties an initial value in the initializer of the structure. Swift won't throw an error, because it also inspects the initializer to determine the initial value—and type—of each property.

5. Classes and Structures

You may start to wonder what the difference is between classes and structures. At first glance, they look identical in form and function, with the exception of the class and struct keywords. There are a number of key differences, though.

Inheritance

Classes support inheritance, whereas structures don't. The following example illustrates this. The inheritance design pattern is indispensable in object-oriented programming and, in Swift, it's a key difference between classes and structures.

In the above example, the Person class is the parent or superclass of the Student class. This means that the Student class inherits the properties and behavior of the Person class. The last line illustrates this. We initialize a Student instance by invoking the custom initializer defined in the Person class.

Copying and Referencing

The following concept is probably the most important concept in Swift you'll learn today, the difference between value types and reference types. Structures are value types, which means that they are passed by value. An example illustrates this concept best.

We define a structure, Point, to encapsulate the data to store a coordinate in a two-dimensional space. We instantiate point1 with x equal to 0 and y equal to 0. We assign point1 to point2 and set the x coordinate of point1 to 10. If we output the x coordinate of both points, we discover that they are not equal.

Structures are passed by value, while classes are passed by reference. If you plan to continue working with Swift, you need to understand the previous statement. When we assigned point1 to point2, Swift created a copy of point1 and assigned it to point2. In other words, point1 and point2 each point to a different instance of the Point structure.

Let's now repeat this exercise with the Person class. In the following example, we instantiate a Person instance, set its properties, assign person1 to person2, and update the firstName property of person1. To see what passing by reference means for classes, we output the value of the firstName property of both Person instances.

The example proves that classes are reference types. This means that person1 and person2 refer to or reference the same Person instance. By assigning person1 to person2, Swift doesn't create a copy of person1. The person2 variable points to the same Person instance person1 is pointing to. Changing the firstName property of person1 also affects the firstName property of person2, because they are referencing the same Person instance.

As I mentioned several times in this article, classes and structures are very similar. What separates classes and structures is very important. If the above concepts aren't clear, then I encourage you to read the article one more time to let the concepts we covered sink in.

Conclusion

In this installment of Swift From Scratch, we've started exploring the basics of object-oriented programming in Swift. Classes and structures are the fundamental building blocks of most Swift projects, and we'll learn more about them in the next few lessons of this series.

In the next lesson, we continue our exploration of classes and structures by taking a closer look at properties and inheritance.

If you want to learn how to use Swift 3 to code real-world apps, check out our course Create iOS Apps With Swift 3. Whether you're new to iOS app development or are looking to make the switch from Objective-C, this course will get you started with Swift for app development. 

 


Tags:

Comments

Related Articles