In the previous articles of this series, we 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 so far and apply that knowledge to the object-oriented concepts available in 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 article.
1. Introduction
In this article, 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 similar, 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 conform to protocols.
The question is "What is the difference between classes and structures?" We'll revisit this question later in this article. 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 that are new to object-oriented programming and it's therefore 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 it's therefore 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 by defining a class. Fire up Xcode and create a new playground. Remove the contents of the playground and add the following class definition.
class Person { }
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 is 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 typeString?
lastName
, a variable property of typeString?
gender
: a constant property of typeString
class Person { var firstName: String? var lastName: String? let gender = "female" }
As the example illustrates, defining properties in a class definition is very 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'll learn about computed properties. As the name implies, stored properties are properties that are stored by the class instance. They are very 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 gender
property an initial value of "female"
. This tells Swift that the gender property is of type String
. Later in this article, we'll take a look at initialization in more detail and explore how it ties in with initializing properties.
Even though we defined the gender
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 gender
property can no longer be modified since we defined the property as a constant property with the let
keyword. This will become clearer later in this article when we discuss initialization.
Methods
We can add behavior or functionality to a class through functions or methods. In many programming languages, the term 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.
class Person { var firstName: String? var lastName: String? let gender = "female" func fullName() -> String { var parts: [String] = [] if let firstName = self.firstName { parts += [firstName] } if let lastName = self.lastName { parts += [lastName] } return " ".join(parts) } }
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.
let john = Person()
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, 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.
john.firstName = "John" john.lastName = "Doe" john.gender = "male"
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 gender
to "male"
. Before we draw any conclusions based on the above example, we need to check for any errors in the playground.
Setting firstName
and lastName
doesn't seem to cause any problems. Assigning "male"
to the gender
property, however, 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 after initialization.
Note that the I emphasized after in the previous sentence. A constant property can be modified during initialization an instance. While the gender
property shouldn't be changed once a Person
instance has been created, the class wouldn't be very useful if we could only instantiate female Person
instances. Let's make the Person
class a bit more flexible.
Initialization
Initialization is a step 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 add an initializer to the Person
class.
class Person { var firstName: String? var lastName: String? let gender = "female" init() { gender = "male" } }
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 gender
property with an initial value. Even though the gender
property is defined as a constant property, we can set its value in the initializer. We set the gender
property by using the property name, but it's also fine to be more explicit and write the following:
init() { self.gender = "male" }
In the above example, self
refers to the instance that's being initialized. This means that self.gender
refers to the gender
property of the instance. We can omit self
, as in the first example, because there's no confusion what property we are referring to. This isn't always the case though. Let me explain what I mean.
Parameters
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, gender
, of type String
.
init(gender: String) { self.gender = gender }
There are two things to note. First, we are required to access the gender
property through self.gender
to avoid ambiguity since the local paramater name is equal to gender
. Second, even though we haven't specified an external parameter name, Swift by default creates an external parameter name that is equal to the local parameter name. The result is the same as if we were to prefix the gender
parameter with a #
symbol.
In the following example, we instantiate another Person
instance by invoking the custom initializer we just defined.
let bart = Person(gender: "male") println(bart.gender) // Outputs "male"
Even though the initial value of the gender
property is set to "female"
in the class definition, by passing a value for the gender
parameter we can assign a custom value to the constant gender
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.
let p1 = Person() let p2 = Person(gender: "male")
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.
struct Wallet { var dollars: Int var cents: Int init() { dollars = 0 cents = 0 } }
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.
class Person { var firstName: String? var lastName: String? let gender = "female" init(gender: String) { self.gender = gender } } class Student: Person { var school: String? } let student = Student(gender: "male")
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 we defined earlier 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 and reference types. Structures are value types, which means that they are passed by value. An example illustrates this concept best.
struct Point { var x: Int var y: Int init(x: Int, y: Int) { self.x = x self.y = y } } var point1 = Point(x: 0, y: 0) var point2 = point1 point1.x = 10 println(point1.x) // Outputs 10 println(point2.x) // Outputs 0
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.
var person1 = Person(gender: "female") person1.firstName = "Jane" person1.lastName = "Doe" var person2 = person1 person1.firstName = "Janine" println(person1.firstName) println(person2.firstName)
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.
Learn More in Our Swift Programming Course
If you're interested in taking your Swift education to the next level, you can take a look at our full course on Swift development.
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 article, we continue our exploration of classes and structures by taking a closer look at properties and inheritance.