Object Oriented Programming in Swift
Learn how object oriented programming works in Swift by breaking things down into objects that can be inherited and composed from. By Cosmin Pupăză.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Object Oriented Programming in Swift
25 mins
Object oriented programming is a fundamental programming paradigm that you must master if you are serious about learning Swift. That’s because object oriented programming is at the heart of most frameworks you’ll be working with. Breaking a problem down into objects that send messages to one another might seem strange at first, but it’s a proven approach for simplifying complex systems, which dates back to the 1950s.
Objects can be used to model almost anything — coordinates on a map, touches on a screen, even fluctuating interest rates in a bank account. When you’re just starting out, it’s useful to practice modeling physical things in the real world before you extend this to more abstract concepts.
In this tutorial, you’ll use object oriented programming to create your own band of musical instruments. You’ll also learn many important concepts along the way including:
- Encapsulation
- Inheritance
- Overriding versus Overloading
- Types versus Instances
- Composition
- Polymorphism
- Access Control
That’s a lot, so let’s get started! :]
Getting Started
Fire up Xcode and go to File\New\Playground…. Type Instruments for Name, select iOS for Platform and click Next. Choose where to save your playground and click Create. Delete everything from it in order to start from scratch.
Designing things in an object-oriented manner usually begins with a general concept extending to more specific types. You want to create musical instruments, so it makes perfect sense to begin with an instrument type and then define concrete (not literally!) instruments such as pianos and guitars from it. Think of the whole thing as a family tree of instruments where everything flows from general to specific and top to bottom like this:

The relationship between a child type and its parent type is an is-a relationship. For example, “Guitar is-a Instrument.” Now that you have a visual understanding of the objects you are dealing with, it’s time to start implementing.
Properties
Add the following block of code at the top of the playground:
// 1
class Instrument {
// 2
let brand: String
// 3
init(brand: String) {
//4
self.brand = brand
}
}
There’s quite a lot going on here, so let’s break it down:
- You create the
Instrumentbase class with theclasskeyword. This is the root class of the instruments hierarchy. It defines a blueprint which forms the basis of any kind of instrument. Because it’s a type, the nameInstrumentis capitalized. It doesn’t have to be capitalized, however this is the convention in Swift. - You declare the instrument’s stored properties (data) that all instruments have. In this case, it’s just the brand, which you represent as a
String. - You create an initializer for the class with the
initkeyword. Its purpose is to construct new instruments by initializing all stored properties. - You set the instrument’s
brandstored property to what was passed in as a parameter. Since the property and the parameter have the same name, you use theselfkeyword to distinguish between them.
You’ve implemented a class for instruments containing a brand property, but you haven’t given it any behavior yet. Time to add some behavior in the form of methods to the mix.
Methods
You can tune and play an instrument regardless of its particular type. Add the following code inside the Instrument class right after the initializer:
func tune() -> String {
fatalError("Implement this method for \(brand)")
}
The tune() method is a placeholder function that crashes at runtime if you call it. Classes with methods like this are said to be abstract because they are not intended for direct use. Instead, you must define a subclass that overrides the method to do something sensible instead of only calling fatalError(). More on overriding later.
Functions defined inside a class are called methods because they have access to properties, such as brand in the case of Instrument. Organizing properties and related operations in a class is a powerful tool for taming complexity. It even has a fancy name: encapsulation. Class types are said to encapsulate data (e.g. stored properties) and behavior (e.g. methods).
Next, add the following code before your Instrument class:
class Music {
let notes: [String]
init(notes: [String]) {
self.notes = notes
}
func prepared() -> String {
return notes.joined(separator: " ")
}
}
This is a Music class that encapsulates an array of notes and allows you to flatten it into a string with the prepared() method.
Add the following method to the Instrument class right after the tune() method:
func play(_ music: Music) -> String {
return music.prepared()
}
The play(_:) method returns a String to be played. You might wonder why you would bother creating a special Music type, instead of just passing along a String array of notes. This provides several advantages: Creating Music helps build a vocabulary, enables the compiler to check your work, and creates a place for future expansion.
Next, add the following method to the Instrument class right after play(_:):
func perform(_ music: Music) {
print(tune())
print(play(music))
}
The perform(_:) method first tunes the instrument and then plays the music given in one go. You’ve composed two of your methods together to work in perfect symphony. (Puns very much intended! :])
That’s it as far as the Instrument class implementation goes. Time to add some specific instruments now.
Inheritance
Add the following class declaration at the bottom of the playground, right after the Instrument class implementation:
// 1
class Piano: Instrument {
let hasPedals: Bool
// 2
static let whiteKeys = 52
static let blackKeys = 36
// 3
init(brand: String, hasPedals: Bool = false) {
self.hasPedals = hasPedals
// 4
super.init(brand: brand)
}
// 5
override func tune() -> String {
return "Piano standard tuning for \(brand)."
}
override func play(_ music: Music) -> String {
// 6
let preparedNotes = super.play(music)
return "Piano playing \(preparedNotes)"
}
}
Here’s what’s going on, step by step:
- You create the
Pianoclass as a subclass of theInstrumentparent class. All the stored properties and methods are automatically inherited by thePianochild class and available for use. - All pianos have exactly the same number of white and black keys regardless of their brand. The associated values of their corresponding properties don’t change dynamically, so you mark the properties as
staticin order to reflect this. - The initializer provides a default value for its
hasPedalsparameter which allows you to leave it off if you want. - You use the
superkeyword to call the parent class initializer after setting the child class stored propertyhasPedals. The super class initializer takes care of initializing inherited properties — in this case,brand. - You override the inherited
tune()method’s implementation with theoverridekeyword. This provides an implementation oftune()that doesn’t callfatalError(), but rather does something specific toPiano. - You override the inherited
play(_:)method. And inside this method, you use thesuperkeyword this time to call theInstrumentparent method in order to get the music’s prepared notes and then play on the piano.
Because Piano derives from Instrument, users of your code already know a lot about it: It has a brand, it can be tuned, played, and can even be performed.
The piano tunes and plays accordingly, but you can play it in different ways. Therefore, it’s time to add pedals to the mix.