Welcome to the fourth tutorial in the series. Today we’ll cover many of Swift’s object-oriented features.

What you will learn…

  • Basic object-oriented concepts with Swift

What you should know…

  • A familiarity with object-oriented programming concepts
  • The basics of the Swift programming language from part one, two and three

Swift doesn’t favour one programming paradigm over another. However it does provide excellent object-oriented features, which we’ll explore the basics of.

Many of the topics covered previously, including functions and structures, will be directly applicable to our coverage of Swift’s object-oriented features. Today’s tutorial will walk you through the steps to build and use a class that models a popular weapon from the Star Wars universe.

If you’re comfortable with the topics covered in part three then getting to grips with the basics of object-oriented programming in Swift shouldn’t be too much of a stretch.

Getting Started

By now you should feel comfortable with the content covered in the first three tutorials. If not then please revisit them:

To try out this tutorial’s code examples, install Xcode and create a playground.
Detail regarding that can be found in the first tutorial.

Classes

A class is declared using the class keyword and its definition is placed within a pair of braces. Its general form looks like this:

class ClassName {
  // Implementation goes here
}

Unlike languages such as Objective-C and C++, Swift does not require you to write a separate interface and implementation file when creating a class. Instead everything is kept within a single file.

For the avoidance of doubt, every time you define a new class or structure, you are effectively defining a brand new type in Swift.

For this reason it is encouraged that all classes and structures use UpperCamelCase names (such as ScorePanel and DataManager) to match the capitalisation of Swift’s standard types. Conversely, any properties and methods you define should use lowerCamelCase names (such as ammunitionCount and engageHyperdrive) to differentiate them from type names.

Functionality is added to a class by defining properties and methods for it. Let’s begin by defining a class that represents an ion cannon:

class IonCannon {
  let capacity = 20
  var ammunition = 20
}

Our class defines two stored properties: capacity and ammunition. They represent the cannon’s ammunition capacity and its current ammunition count respectively. Since the capacity can’t be changed it has been defined as a constant.

Instantiating a Class

A class instance can be created with initializer syntax:

let cannon = IonCannon()

If you think back to the section on structures in part three, you may reasonably assume that a memberwise initializer is provided for each class you define. For example, you may try to create an instance of your ion cannon with the following:

let cannon = IonCannon(ammunition: 15)

However, unlike structures, class instances do not receive a default memberwise initializer. The line of code above would result in a compile-time error.

Classes are Reference Types

Until now, every type we’ve looked at, including structures, has been a value type. Classes however are reference types.

Remember, a value type is copied when it is assigned to a variable or constant, or when being passed to a function. With classes, a reference to the same instance is used instead.

Here’s an example:

let cannon1 = IonCannon()
let cannon2 = cannon1

The code above creates two constants that point to the exact same IonCannon instance. We can prove this by modifying the value of one’s stored property and seeing the change reflected in the other:

cannon1.ammunition = 10
print(“cannon1.ammunition: \(cannon1.ammunition)”)
print(“cannon2.ammunition: \(cannon2.ammunition)”)

The following will be output:

cannon1.ammunition: 10
cannon2.ammunition: 10

While structures and classes are very similar, this is one major difference between the two and should be a consideration when deciding whether it’s best to define a structure or class for a particular type you are modelling.

If you’re familiar with languages such as C++ or Objective-C then you’ll be used to creating pointers to refer to your class instances. A Swift constant or variable that refers to an instance of a class is similar to a pointer in these languages but does not require you to write an asterisk (*) to indicate that you’re creating a class reference. Instead, in Swift, these references are defined like any other constant or variable. Languages such as ActionScript 3 and Java work in a similar fashion to Swift in this regard.

Classes As Constants

We saw in part three that declaring a constant instance of a structure will prevent you from modifying any variable properties belonging to that instance.

The same isn’t true of class instances, which are reference types. If you assign an instance of a reference type to a constant, you can still change any variable properties that belong to it. You can’t however, assign your constant to another class instance after it has been set. Let’s look at a few quick examples:

let cannon = IonCannon()
cannon.ammunition = 10

The code snippet above is perfectly valid. Even though we use a constant to refer to our IonCannon instance, we are still able to modify properties belonging to it.

However, we can’t set our cannon constant to any other IonCannon instances:

let cannon = IonCannon()
cannon.ammunition = 10
cannon = IonCannon()

The line above will result in the following compile-time error: Cannot assign to value: 'cannon' is a 'let' constant.

Identity Operators

When working with reference types such as class instances, it’s possible for multiple constants and variables to refer to the exact same instance of a class. Here’s an example:

let cannon1 = IonCannon()
let cannon2 = IonCannon()
let cannon3 = cannon1

In the code above, the constants cannon1 and cannon3 refer to the exact same instance of the IonCannon class. The cannon2 constant however refers to a different instance of IonCannon.

On occasions, you may want to know if two constants or variables refer to the exact same class instance. Swift provides the "identical to" operator (===) for this purpose. Try entering the following into your playground:

let cannon1 = IonCannon()
let cannon2 = IonCannon()
let cannon3 = cannon1

var sameInstance = cannon1 === cannon2
println(“cannon1 and cannon2 refer to the same instance: \(sameInstance)”)

sameInstance = cannon1 === cannon3
println(“cannon1 and cannon3 refer to the same instance: \(sameInstance)”)

sameInstance = cannon2 === cannon3
println(“cannon2 and cannon3 refer to the same instance: \(sameInstance)”)

It will result in the following being output:

cannon1 and cannon2 refer to the same instance: false
cannon1 and cannon3 refer to the same instance: true
cannon2 and cannon3 refer to the same instance: false

As you might expect, Swift also provides the "not identical to" operator (!==) to check if two constants or variables are referring to different instances.

Properties

We spent considerable time discussing properties when covering structures in part three. The good news is that everything that applies to properties for structures also applies to classes.

We’ll spend just a few moments on the subject to help build out our example IonCannon class and also to highlight a few things of note.

Type Properties

Remember, type properties are useful for defining values that are universal to all instances of a particular type. Type properties are defined using the static keyword.

While we’re on the subject, let’s change our class’ capacity stored property to a type property:

class IonCannon {
  static let capacity = 20
  var ammunition = 20
}

It’s worth noting that for computed type properties of a class, you can use the class keyword instead of static. Doing so will allow a subclass to override the superclass’ implementation. We’ll cover this at a later date in part five.

We can also explicitly set our variable property, ammunition, to the value of our static constant property, capacity. Make the following change to your class:

class IonCannon {
  static let capacity = 20
  var ammunition = IonCannon.capacity
}

This will make our code more maintainable.

Property Observers

To ensure our ion cannon example is as robust as possible, it’s important that the ammunition property is never a negative value or has a value greater than the cannon’s capacity.

For completeness, add a property observer that constrains the value of the ammunition property:

class IonCannon {
  static let capacity = 20
  var ammunition = IonCannon.capacity {
    didSet {
      if ammunition < 0 {
        ammunition = 0
      } else if ammunition > IonCannon.capacity {
        ammunition = IonCannon.capacity
      }
    }

  }
}

Verify that everything is working as expected by attempting to set invalid values for the ammunition property:

let cannon = IonCannon()
cannon.ammunition = -1
print(“cannon.ammunition: \(cannon.ammunition)”)
cannon.ammunition = 21
print(“cannon.ammunition: \(cannon.ammunition)”)
cannon.ammunition = 10
print(“cannon.ammunition: \(cannon.ammunition)”)

The following will be output:

cannon.ammunition: 0
cannon.ammunition: 20
cannon.ammunition: 10

As you can see from the results above, the value of the instance’s ammunition property is always set to an integer value between 0 and 20 inclusive.

Methods

Methods have the exact same syntax as functions, which we covered in part three. A method belongs to a particular class and provides a means to access and modify an instance of that class in some way.

Let’s add a simple method to our class that is used to reload the cannon’s ammunition:

class IonCannon {
  static let capacity = 20
  var ammunition = IonCannon.capacity {
    didSet {
      if ammunition < 0 {
        ammunition = 0
      } else if ammunition > IonCannon.capacity {
        ammunition = IonCannon.capacity
      }
    }
  }
 
  func reload() {
    ammunition = IonCannon.capacity
  }

}

We can now easily reset our cannon to full capacity by calling the reload method. Here’s an example:

let cannon = IonCannon()
cannon.ammunition = 10
print(“cannon.ammunition: \(cannon.ammunition)”)
cannon.reload()
print(“cannon.ammunition: \(cannon.ammunition)”)

You should see the following output in the sidebar:

cannon.ammunition: 10
cannon.ammunition: 20

In other words, calling reload has set the cannon’s capacity from 10 back to 20.

Parameters

As with functions, you can optionally define one or more typed parameters for class methods.

Let’s add a method to our IonCannon class that fires a burst of ion pulses. The method takes a single parameter that is used to specify the number of ion pulses to fire:

class IonCannon {
  static let capacity = 20
  var ammunition = IonCannon.capacity {
    didSet {
      if ammunition < 0 {
        ammunition = 0
      } else if ammunition > IonCannon.capacity {
        ammunition = IonCannon.capacity
      }
    }
  }
 
  func reload() {
    ammunition = IonCannon.capacity
  }
 
  func fireBurstOf(amount: Int) {
    ammunition -= amount
  }

}

We’ve just added a method named fireBurstOf(_:) that simulates our ion cannon firing a specified number of pulses. Calling this method will deplete the instance’s ammunition property.

Let’s use our method to fire 4 ion bursts at the incoming Imperial fleet:

let cannon = IonCannon()
cannon.fireBurstOf(4)

We can verify that the pulses were fired by querying our instance’s ammunition property:

print(“The cannon has enough charge remaining to fire \(cannon.ammunition) more ion bursts.”)

The line above will result in the following being sent to the appropriate output:

The cannon has enough charge remaining to fire 16 more ion bursts.

Call the method again to fire 2 more bursts:

cannon.fireBurstOf(2)

Your ion cannon’s ammunition property will now have a value of 14. Hopefully you took out a few Star Destroyers with those shots.

It’s possible to provide multiple versions of a method. Here’s an alternative version of our fireBurstOf(_:) method that expects two parameters – amount and numberOfTimes:

class IonCannon {
  static let capacity = 20
  var ammunition = IonCannon.capacity {
    didSet {
      if ammunition < 0 {
        ammunition = 0
      } else if ammunition > IonCannon.capacity {
        ammunition = IonCannon.capacity
      }
    }
  }
 
  func reload() {
    ammunition = IonCannon.capacity
  }
 
  func fireBurstOf(amount: Int) {
    ammunition -= amount
  }
 
  func fireBurstOf(amount: Int, numberOfTimes: Int) {
    ammunition -= (amount * numberOfTimes)
  }

}

The numberOfTimes parameter is used to specify multiple repetitions of our burst of ion pulses. For example, here’s how to fire off a burst of three ion pulses, four times:

let cannon = IonCannon()
cannon.fireBurstOf(3, numberOfTimes: 4)

Notice that we explicitly stated the name of the method’s second parameter when making our call. This should seem odd to you as we didn’t explicitly define an external name for our second parameter. In Swift, the default behaviour of local and external parameter names in methods differs from functions. Swift gives the first parameter in a method a local parameter name by default, but gives subsequent parameter names both a local and external parameter name.

The reason for this is that Swift attempts to make method naming and calling similar to that of Objective-C. In Objective-C, the name of a method typically refers to the method’s first parameter using a preposition such as with, for, by, of etc. We can see this in our fireBurstOf(_:numberOfTimes:) method. The use of the Of preposition helps the method to be read as a sentence when it is called.

This naming and calling convention will be familiar to anyone who has coded in Objective-C. Those from other programming languages will likely take time to warm to it. It’s actually a very expressive feature of both Swift and Objective-C and when used properly can result in code that’s extremely easy to read.

Return Values

As you would expect, values can be returned from methods in exactly the same manner as functions.

As a quick example, let’s modify our fireBurstOf(_:numberOfTimes:) method so that it returns the number of ion pulses that were actually fired. This will be useful since the requested number of pulses to fire may actually be greater than the cannon’s current ammunition level.

Change your method’s definition to this:

func fireBurstOf(amount: Int, numberOfTimes: Int) -> Int {
  var remainingShots = amount * numberOfTimes
  var actuallyFired = 0
  while ammunition > 0 && remainingShots > 0 {
    ammunition--
    remainingShots--
    actuallyFired++
  }
 
  return actuallyFired

}

Cool! We can now tell exactly how many ion pulses were fired on each attempt. Here’s a quick example of how to use our updated method:

let cannon = IonCannon()
print(“Available ion pulses: \(cannon.ammunition)”)
var pulsesFired = cannon.fireBurstOf(17, numberOfTimes: 1)
print(“Attempting to fire 17 ion pulses: \(pulsesFired) actually fired with \(cannon.ammunition) remaining.”)
pulsesFired = cannon.fireBurstOf(5, numberOfTimes: 1)
print(“Attempting to fire 5 ion pulses. \(pulsesFired) actually fired with \(cannon.ammunition) remaining.”)

The following will be output to the sidebar:

Available ion pulses: 20
Attempting to fire 17 ion pulses: 17 actually fired with 3 remaining.
Attempting to fire 5 ion pulses: 3 actually fired with 0 remaining.

As you can see from the final line above. We attempted to fire 5 ion pulses when there was only enough ammunition for 3. Therefore our fireBurstOf(_:numberOfTimes:) method returned a value of 3 indicating that only three ion pulses were actually fired.

Default Parameter Values

As with functions, a default value can be assigned to parameters of a method. We can default our fireBurstOf(_:numberOfTimes:) method’s numberOfTimes parameter to 1. Doing so will allow us to remove our class’ original fireBurstOf(_:) method while still providing the same API. Make the following changes:

func fireBurstOf(amount: Int) {
  ammunition -= amount
}
 
func fireBurstOf(amount: Int, numberOfTimes: Int = 1) -> Int {
  var remainingShots = amount * numberOfTimes
  var actuallyFired = 0
  while ammunition > 0 && remainingShots > 0 {
    ammunition–
    remainingShots–
    actuallyFired++
  }
 
  return actuallyFired
}

Notice that we added a default value of 1 to the fireBurstOf(_:numberOfTimes:) method’s definition above. It’s an easy code change to miss.

Thanks to our default parameter, we can still make the following two types of calls even though we’ve removed one of our methods:

  • fireBurstOf(_:)
  • fireBurstOf(_:numberOfTimes:)

Here’s a concrete example:

let cannon = IonCannon()
cannon.fireBurstOf(5) // Will fire 5 ion pulses
cannon.fireBurstOf(5, numberOfTimes: 1) // Will also fire 5 ion pulses
cannon.fireBurstOf(5, numberOfTimes: 2) // Will fire 10 ion pulses

Type Methods

As well as instance methods, you can also define methods that belong to the class itself rather than an instance of the class. Such methods are known as type methods and are specified by placing the static keyword before the method’s func keyword.

Those familiar with object-oriented languages such as Java and ActionScript 3 will be familiar with the concept of static methods. A type method is Swift’s equivalent of a static method.

Let’s work through a simple example. We’ll write a type method within our IonCannon class that returns a text description stating how well the Rebel Alliance are faring against the Galactic Empire. The description will be generated based on the number of Star Destroyers that have been successfully hit by all ion cannon instances.

Start by adding a type property that keeps track of the number of Star Destroyers that have been successfully hit by our ion cannons:

class IonCannon {
  static let capacity = 20
  var ammunition = IonCannon.capacity {
    didSet {
      if ammunition < 0 {
        ammunition = 0
      } else if ammunition > IonCannon.capacity {
        ammunition = IonCannon.capacity
      }
    }
  }
  static var directHits = 0
 
  :
  :
}

We’ll also add a few lines to our fireBurstOf(_:numberOfTimes:) method so that it randomly attempts to increment the directHits type property each time an ion pulse is fired. Update your method to look like this:

import Foundation
 
class IonCannon {
  static let capacity = 20
  var ammunition = IonCannon.capacity {
    didSet {
      if ammunition < 0 {
        ammunition = 0
      } else if ammunition > IonCannon.capacity {
        ammunition = IonCannon.capacity
      }
    }
  }
  static var directHits = 0
 
  :
  :
 
  func fireBurstOf(amount: Int, numberOfTimes: Int = 1) -> Int {
    var remainingShots = amount * numberOfTimes
    var actuallyFired = 0
    while ammunition > 0 && remainingShots > 0 {
      ammunition–
      remainingShots–
      actuallyFired++
 
      if arc4random_uniform(10) == 0 {
        IonCannon.directHits++
      }

    }
 
    return actuallyFired
  }
}

Notice on the first line of code above that we imported iOS’s Foundation library. Doing so provides support for random number generation via the arc4random_uniform(_:) global function.

Finally, we’ll add our type method to the class. This method will examine the directHits type property and return a text description based on the number of Star Destroyers that have been successfully hit by all ion cannon instances. Add the following method at the end of your class:

static func report() -> String {
  switch directHits {
  case 0...2:
    return “The rebel fleet has little chance”
  case 3...5:
    return “Some rebel transports may get away from Hoth”
  default:
    return “The entire rebel fleet will likely escape”
  }
}

And here’s how we’d use our updated class:

let cannon1 = IonCannon()
let cannon2 = IonCannon()
let cannon3 = IonCannon()
cannon1.fireBurstOf(5, numberOfTimes: 3)
cannon2.fireBurstOf(12)
cannon3.fireBurstOf(7, numberOfTimes: 2)
print(IonCannon.report())

The code above creates three ion cannon instances and fires off a burst of ion pulses from each. We then obtain a report to see if the cannon fire was enough to clear the Empire’s Star Destroyers from the path of the fleeing Rebel fleet.

Pay particular attention to how we call the report method. Since it’s a type method we make a call directly on the class rather than an instance of the class. Here’s the call again:

IonCannon.report()

We can also find out the actual number of direct hits by obtaining the value of the class’ directHits type property:

print(IonCannon.directHits)

You can also use the class keyword in place of static when defining a type method. Doing so will allow a subclass to override the superclass’ implementation of the type method. This will be covered at a later date in part five.

Initializers

In part three we covered initialization of structures including how to write custom initializers. In this tutorial we have seen that class instances are created using initializer syntax similar to structure initialization:

let cannon = IonCannon()

We can also write one or more custom initializer methods for a class, which can be called to create a new instance of a particular class. Custom initializers are useful when certain stored properties of an instance need to be set or any further setup within the instance is required.

If you have a background in object-oriented programming languages such as Java, C++, or ActionScript 3 then you’ll be familiar with class constructors. Initializers in Swift perform the same role.

An initializer is required if a default value has not been assigned to all of a class’ stored properties. The only exception to this are optional property types, which can be assigned a value at a later point.

Here’s how our IonCannon class might look if its ammunition property had not been assigned a default value along with its declaration:

class IonCannon {
  static let capacity = 20
  var ammunition :Int {
    didSet {
      if ammunition < 0 {
        ammunition = 0
      } else if ammunition > IonCannon.capacity {
        ammunition = IonCannon.capacity
      }
    }
  }
  static var directHits = 0
 
  init() {
    ammunition = IonCannon.capacity
  }

 
  :
  :
}

In the example above we set the initial value of our ammunition property within an initializer. Of course, if your class’ stored properties always take the same initial value then you really should provide a default value rather than write an initialiser.

If you’re an Objective-C developer then you’ll already be familiar with initialisers. However, unlike Objective-C, Swift initialisers do not explicitly return a value.

A more practical use of initializers is when you wish to customise the initialization of a class instance with input parameters. Let’s improve our IonCannon class so that we can set the cannon’s initial amount of ammunition during initialization:

class IonCannon {
  static let capacity = 20
  var ammunition = IonCannon.capacity {
    didSet {
      if ammunition < 0 {
        ammunition = 0
      } else if ammunition > IonCannon.capacity {
        ammunition = IonCannon.capacity
      }
    }
  }
  static var directHits = 0
 
  init(withAmmunition ammunition: Int = IonCannon.capacity) {
    self.ammunition = ammunition
    if self.ammunition > IonCannon.capacity {
      self.ammunition = IonCannon.capacity
    }
  }

 
  func reload() {
    ammunition = IonCannon.capacity
  }
 
  func fireBurstOf(amount: Int, numberOfTimes: Int = 1) -> Int {
    var remainingShots = amount * numberOfTimes
    var actuallyFired = 0
    while ammunition > 0 && remainingShots > 0 {
      ammunition–
      remainingShots–
      actuallyFired++
 
      if arc4random_uniform(10) == 0 {
        IonCannon.directHits++
      }
    }
 
    return actuallyFired
  }
 
  static func report() -> String {
    switch directHits {
    case 0...2:
      return “The rebel fleet has little chance”
    case 3...5:
      return “Some rebel transports may get away from Hoth”
    default:
      return “The entire rebel fleet will likely escape”
  }
}

Our custom initialiser takes a single parameter that is used to set the instance’s initial amount of ammunition. The initialiser’s body also safeguards against invalid values by ensuring that the specified ammunition value isn’t a negative integer or greater than the cannon’s capacity.

Also, our initialiser has both a local (ammunition) and an external (withAmmunition) parameter name. As with functions and methods, the local name is used within the initialiser’s body, while the external name is used when calling the initialiser. The external parameter name is particularly important when calling initialisers since they do not have an identifying function name before their parentheses in the same way that functions and methods do.

Finally, you should note that our custom initialiser’s parameter also has a default value of IonCannon.capacity specified. Without this we’d need to write a second initialiser that accepted no parameters and simply initialised the ammunition property to a default value.

With our initialiser in place, we can now create ion cannon instances and provide each with a specified initial amount of ammunition. Here are some concrete examples:

let cannon1 = IonCannon()
let cannon2 = IonCannon(withAmmunition: 15)
let cannon3 = IonCannon(withAmmunition: 40)

The Self Property

We’ve mentioned the self property before when covering structures, but for the avoidance of doubt we’ll spend a few moments with it again.

Just like structures, every class instance has an implicit property named self. This property refers to the current instance within its own methods and can be used to refer to a property or method of that instance. As an example, take a look at our class’ reload method:

func reload() {
  ammunition = IonCannon.capacity
}

We can rewrite this method to explicitly use self when referring to our instance’s ammunition property. Here’s how it would look:

func reload() {
  self.ammunition = IonCannon.capacity
}

However, for most situations you won’t need to explicitly use self. Swift will simply assume that you are referring to a property or method of the current instance.

The self property is useful when a parameter name of a method is the same as a property of the instance. In such situations the parameter name takes precedence and the property can only be referred to via self. We can actually see this in our class’ initialiser, where its internal parameter has the same name as the class’ ammunition property. To remove any ambiguity, the self prefix was used to identify the property:

init(withAmmunition ammunition: Int = IonCannon.capacity) {
  self.ammunition = ammunition
  if self.ammunition > IonCannon.capacity {
    self.ammunition = IonCannon.capacity
  }
}

Objective-C developers will be familiar with the self keyword. It is commonly know as this in most other programming languages.

Next Time

We’ve covered the basics of object oriented programming. While Swift uses different naming conventions the principles are more-or-less identical to those found in other object-oriented languages. If you’re coming from a language such as Java, C++, TypeScript or ActionScript 3 then writing and working with classes in Swift should be straight forward. Even if you’re coming from JavaScript, which lacks much of the syntactic sugar that other object-oriented languages provide, you shouldn’t find things too much of a stretch to grasp.

There are still a few object-oriented features still to cover. Next time we’ll spend some time looking at inheritance. You may also have noticed that we haven’t yet covered access control. Swift’s access control is different from that found in most languages – it is file-based rather than class-based – and therefore deserves more space than this particular tutorial can afford it. Finally, we’ll also spend some time looking at class deinitialization, which allows us to perform any manual clean-up when a class instance is no longer needed.

See you in part five.