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
Instrument
base class with theclass
keyword. 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 nameInstrument
is 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
init
keyword. Its purpose is to construct new instruments by initializing all stored properties. - You set the instrument’s
brand
stored property to what was passed in as a parameter. Since the property and the parameter have the same name, you use theself
keyword 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
Piano
class as a subclass of theInstrument
parent class. All the stored properties and methods are automatically inherited by thePiano
child 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
static
in order to reflect this. - The initializer provides a default value for its
hasPedals
parameter which allows you to leave it off if you want. - You use the
super
keyword 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 theoverride
keyword. 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 thesuper
keyword this time to call theInstrument
parent 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.