Lesson 7

Object-Oriented Programming with Swift

Over the years, computer application developers have developed various strategies to create applications that can solve complex problems. One of the earliest approaches to problem solving was the concept of structured programming.

Structured programming (which predates object-oriented programming) centered on a divide-and-conquer philosophy. A complex program was broken down into a set of tasks, and then each task into a set of simpler sub-tasks. A key feature of structured programming is that there is a clear separation between data and the code that operates on that data.

Structured programming is still in use in some types of applications today, but it has a few drawbacks:

  • People generally think of data (account numbers) and what they can do with it (compute balance, interest, and so on) as related concepts. It is not natural to think of them in isolation.
  • Programmers were constantly reinventing the wheel, creating solutions for things that had been solved over and over again by others. Structured programming did not address the need to reuse existing functions (either written by you or someone else) conveniently.
  • A new approach to programming, object-oriented programming (OOP), was created. Essentially, OOP tries to address the deficiencies in the structured programming model by:
    • Providing techniques to achieve re-use of software components.
    • Coupling data with the functions that act on them.

Core to object-oriented programming is the idea of treating data and functions that act upon them as an independent entity known as an object.

Creating Classes with Swift

A class can be thought of as a template or blueprint of an object. This is best understood by an example. If you were to go down to your local car dealer, you would likely find several cars there. Each of these cars share some common characteristics with each other; for instance, each has seats, wipers, four wheels, and so on. Looking at this situation from an object-oriented perspective, you can say that each of these cars is an instance of a class of objects called automobiles. The Automobile class (see Figure 7.1) could then be thought to define some characteristics that are common to each instance (such as the fact that each car has four wheels).

Diagram displaying different OBJECTS: Ford Focus, BMW X5, Aston Martin DB19 and their CLASS: Automobiles with some common characteristics listed.

Figure 7.1

Classes are created in swift using the class keyword followed by the name of the class. Unlike Objective-C, the external interface and implementation of a class is contained in a single file. A bare-bones Automobile class would resemble the following:

class Automobile : NSObject
{
}

The statements that will make up the body of the class are contained within the pair of curly braces. Just like Objective-C, Swift classes generally inherit from NSObject either directly or indirectly.

Properties

Common characteristics between the various instances of the Automobile class can be easily represented using variables; for example, the number of wheels could be represented by an integer variable named wheelCount and so on. Table 7.1 lists the characteristics of the Automobile class and the equivalent variables that could be used to represent them.

Table 7.1 Characteristics of the Automobile Class

Characteristic Variable
Has 4 wheels Int wheelCount
Has seats Bool hasSeats
Has 1 steering wheel Int numberOfSteeringWheels
Has windows Bool hasWindows
Has brake pedal Bool hasBrakePedal

Unlike Objective-C, Swift does not have an explicit @property syntax. Properties in Swift are simply public member variables of the class. With this in mind, the Automobile class now becomes:

class Automobile : NSObject
{
    var wheelCount:Int?
    var hasSeats:Bool?
    var numberOfSteeringWheels:Int?
    var hasWindows:Bool?
    var hasBreakPedal:Bool?
}

Notice how every variable is declared as an optional type. The class does not have an init() method at this point so the default values of all these variables will automatically be nil, and you need optionals to handle nil values in Swift.

Methods

To be compliant with the principles of object-oriented design, this Automobile class must also define some operations that do something with these variables (see Figure 7.2). Whatever these operations may be, each concrete instance of the Automobile class will be able to perform them.

Diagram displaying different OBJECTS: Ford Focus, BMW X5, Aston Martin DB19 and their CLASS: Automobiles with some common characteristics and operations listed.

Figure 7.2

These operations are best thought of as commands you could give to a car (instance of Automobile class). This is perhaps where object-oriented solutions differ from real-world situations. In the real world, you can't command a car to drive itself (except in the movies); you need to drive the car. In an object-oriented world, however, the car would drive itself and all you would have to do is tell the car to start driving. Table 7.2 lists a few possible operations that the Automobile class could define.

Table 7.2 Operations in the Automobile Class

Operation Description
rollDownWindows The car rolls down all its windows.
stopMoving The car stops moving.
startMoving The car starts moving.

Just as you use variables to represent the common characteristics, each of these operations would be represented using blocks of code (methods). These blocks of code would operate on the data (variables) within the class to achieve the desired outcome. The following is the modified definition of the Automobile class:

class Automobile : NSObject
{
    var wheelCount:Int?
    var hasSeats:Bool?
    var numberOfSteeringWheels:Int?
    var hasWindows:Bool?
    var hasBreakPedal:Bool?
    var speed:Int?
    func rollDownWindows () {
        println("windows are now open");
    }
    func stopMoving() {
        speed = 0
        println("car has stopped moving")
    }
    func startMoving() {
        speed = 10
        println("car is moving")
    }
}

An initializer is a special method in a class that is used to create an instance of the class. This is similar to the concept of a constructor in other languages. The process of initialization typically involves setting up default values for member variables and any other setup tasks that may be necessary. Unlike Objective-C, initializers in Swift do not return a value.

When it comes to specifying default values for member variables, you can either specify them at the time of declaration or set them up in an initializer. The initializer for the Automobile class would be:

init (numWheels:Int, seats:Bool,
       steeringWheelCount:Int, windows:Bool,
       breakPedal:Bool) {
        wheelCount = numWheels
        hasSeats = seats
        numberOfSteeringWheels = steeringWheelCount
        hasWindows = windows
        hasBreakPedal = breakPedal
    }

You can also create an initializer without any parameters; however, because the Automobile class inherits from NSObject, you will end up overriding the default no-parameter initializer provided by NSObject. A default initializer for the Automobile class would resemble the following:

override init() {
        wheelCount = 4
        hasSeats = true
        numberOfSteeringWheels = 1
        hasWindows = true
        hasBreakPedal = true
    }

A deinitializer is a method that is called immediately before a class is deallocated. Deinitializers are written with the deinit keyword, and are called automatically for you.

deinit {
}

Swift uses ARC (Automatic Reference Counting) to manage your memory for you so you do not usually need to use deinitializers. However, if you are managing your own resources outside of ARC, you will need to use a deinit method to free these resources.

To find out more about ARC, refer to the Automatic Reference Counting guide available at: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

Instantiating Objects

When it comes to using a class, in most cases you need to instantiate it into a concrete object first. All subsequent interaction will be with the object and not the class. The Automobile class in this example is not an actual car; it is just the definition of what a car should be.

The following example shows how an instance of the Automobile class may be instantiated and used:

var bmwThreeSeries: Automobile = Automobile(numWheels:4,
                        seats:true, steeringWheelCount:1,
                        windows:true, breakPedal:true)
bmwThreeSeries.rollDownWindows()
bmwThreeSeries.startMoving()
bmwThreeSeries.stopMoving()

Inheritance

When developing an application, you are likely to create more than one class. The classes you define are likely to have some relationships with each other. Object-oriented programming allows you to specify different types of relationships between classes.

The concept of inheritance implies that a new class can be created that inherits the functionality of an existing class. This new class will provide the functionality of the parent class and provide some additional functionality of its own. Inheriting from a base class is known as subclassing. By subclassing an existing class, the designer of an object-oriented solution is reusing the functionality present in an existing class and not duplicating it. The parent class is commonly referred to as the base class, and the child as the subclass.

As an example, consider a hypothetical class Dog (see Figure 7.3). Such a class could either be created in isolation, or more likely inherit from a more general class Mammal. The attributes and methods present in the Mammal class would be a part of the Dog class. In addition, the Dog class would add a few attributes and methods of its own.

Diagram displaying two OBJECTS: Ben's dog, Jane's dog and its relationship with CLASS: Dog(is-a mammal) and CLASS: Mammal with different attributes and methods listed.

Figure 7.3

When you use inheritance to create a relationship between two classes, you are essentially creating an is-a relationship between them. In the preceding example, a Dog is a Mammal. When one class inherits from another, the parent class is known as the superclass and the derived class is known as the subclass.

To indicate a class inherits from a superclass, you indicate the name of the superclass after the subclass, separating the two names with a colon. For example, if Mammal is the superclass, and Dog a subclass, then this relationship can be defined in Swift as follows:

class Mammal : NSObject{
    var isMale:Bool = false
    func play() {
        println("Mammal's play() called")
    }
    func rest() {
        println("Mammal's rest() called")
    }
    func eat() {
        println("Mammal's eat() called")
    }
}
class Dog : Mammal{
    var hasFourLegs:Bool = true
    func bark(){
        println("Dog's bar() called")
    }
}

A subclass can provide its own implementation of a method that is defined in a superclass. This behavior is called method overriding, and the subclass's version of the method will be used instead of the superclass. When overriding a superclass method, Swift requires that you prefix your overriding version in the subclass with the override keyword. Within your overriding version, you can access the variables and methods of the superclass using the super keyword. In the following code snippet, the Dog class overrides the eat method, which it would have otherwise inherited from Mammal:

class Dog : Mammal{
    var hasFourLegs:Bool = true
    override func eat() {
        super.eat()
        println("Dog's eat() called")
    }
    func bark(){
        println("Dog's bar() called")
    }
}

The overriding version of eat in the Dog class first calls the superclass's version of eat. This is not strictly required but is a good idea. If you want to prevent a method from being overridden in a subclass, you need to append the final keyword before the method declaration.

Computed Properties

Swift provides the concept of computed properties. These are similar to custom getters and setters in Objective-C.

A getter is a method that provides read-only access to a private member variable of an object. A setter is a method that allows another object to change the value of a private member variable.

In object-oriented design it is common practice to create private member variables in a class and selectively provide getters/setters to define the operations that can be performed on these variables. This practice is known as encapsulation and allows one to use a class without knowing the details about how specific functionality is implemented in the class. Encapsulation also provides the class designer with a degree of control over how the class will be used.

It is not necessary to have both getter and setter methods for a member variable. Providing just a getter method (without a setter method) would in effect make the underlying member variable read-only.

Computed properties do not actually store a value; instead, you provide a getter and (an optional) setter method that compute the value of the property based on other properties of the class. This is illustrated in the following example:

class Rectangle : NSObject {
    var length:Double
    var breadth:Double
    init(length:Double, breadth:Double) {
        self.length = length
        self.breadth = breadth
    }
    var area : Double {
        get{
           return length * breadth
        }
    }
}

Enumerations

An enumeration is a data type that groups a set of named values. The named values are referred to as elements of the enumeration. Unlike C, Swift enumerations can contain computed properties, initializers, and member methods.

Enumerations in Swift are defined using the enum keyword, and the member values within that enumeration are prefixed with the case keyword, as shown here:

enum EmployeeType {
    case CEO
    case CTO
    case Manager
    case Receptionist
    case Developer
    case ProductOwner
}

To use this enumeration, you will need to declare an appropriate variable and assign it one of the values in the enumeration as follows:

var acmeEmployee : EmployeeType = EmployeeType.Developer

Protocols

A protocol can be thought of as a contract that a class agrees to abide by. Technically speaking, the class is said to implement the protocol in question. But what form does this contract take?

This contract (protocol) is basically a list of methods. These methods can be grouped as either required or optional. Any class that wishes to conform to a protocol must provide implementations of all required methods in the protocol. A class can implement multiple protocols (see Figure 7.4), and multiple classes may implement a given protocol.

Diagram displaying CLASS 1, 2, 3, 4 and their relationship with Protocols (Contract) 1, 2 they implement.

Figure 7.4

Just because a class implements a protocol does not mean that the class cannot have additional methods of its own (in addition to the ones defined in the protocol). The manner in which protocols are used depends on the designer of the object-oriented system. In other object-oriented languages like C++, protocols are known as interfaces.

Protocols are defined in Swift using the protocol keyword:

protocol MessageListener {
}

A class conforms to a protocol by including the names of the protocol in its declaration after a colon. A class can conform to multiple protocols by listing the names of the protocols separated by commas. If a class has a superclass, the name of the superclass must be listed before any protocols. The following code snippet lists a class called NetworkManager that is a subclass of NSObject (inheritance) and implements the MessageListener protocol:

class NetworkManager: NSObject, MessageListener {
}

If a protocol contains member variables (property definitions), then conforming classes will need to provide appropriate properties with the same name in their declarations. The protocol does not specify the manner in which a conforming class may implement properties. A conforming class could provide a stored property or a computed property implementation. A protocol, however, does specify whether each property must be gettable, settable, or both. The following snippet shows the MessageListener protocol with a few property definitions as well as the corresponding implementation in the NetworkManager class:

import Foundation
protocol MessageListener {
    var hasNewMessage:Bool {get}
    var messagePollInterval:Int {get set}
}
class NetworkManager: NSObject, MessageListener {
    private var isDirty : Bool = false
    private var pollInterval : Int = 10
    var hasNewMessage : Bool {
        get {
            return isDirty
        }
    }
    var messagePollInterval: Int {
        get {
            return pollInterval
        }
        set {
            self.pollInterval = newValue
        }
    }
}

A protocol can also contain method names that conforming classes must implement. The following code snippet builds on the MessageListener protocol and NetworkManager class by adding methods:

protocol MessageListener {
    var hasNewMessage:Bool {get}
    var messagePollInterval:Int {get set}
    func beginListening()
    func endListening()
}
class NetworkManager: NSObject, MessageListener {
    private var isDirty : Bool = false
    private var pollInterval : Int = 10
    var hasNewMessage : Bool {
        get {
            return isDirty
        }
    }
    var messagePollInterval: Int {
        get {
            return pollInterval
        }
        set {
            self.pollInterval = newValue
        }
    }
    func beginListening() {
        println("NetworkManager beginListening() is called")
    }
    func endListening() {
        println("NetworkManager endListening() is called")
    }
}

If a method or property is prefixed with the optional attribute in the protocol, then conforming classes need not provide implementations for these. If a protocol contains an optional property or method in its definition, then the entire protocol must be marked with the @objc attribute, as you can see in the following code snippet:

@objc protocol ConnectionDelegate {
    optional func setupConnectionAttributes(ipaddress:String, port:Int) -> Bool
}
class NetworkManager: NSObject, MessageListener, ConnectionDelegate {
    private var isDirty : Bool = false
    private var pollInterval : Int = 10
    var hasNewMessage : Bool {
        get {
            return isDirty
        }
    }
    var messagePollInterval: Int {
        get {
            return pollInterval
        }
        set {
            self.pollInterval = newValue
        }
    }
    func beginListening() {
        println("NetworkManager beginListening() is called")
    }
    func endListening() {
        println("NetworkManager endListening() is called")
    }
    // NOTE: this class does not need to provide an implementation for
    // the optional method in the ConnectionDelegate protocol, but it does
    // in this case
    func setupConnectionAttributes(ipaddress:String, port:Int) -> Bool{
        return false
    }
}

Try It

In this Try It, you create a new Swift playground and build a class called Calculator that performs arithmetic calculations.

Lesson Requirements

  • Launch Xcode.
  • Create a new Swift playground.
  • Create a class called Calculator that performs arithmetic calculations.
  • Display the results in the console.

Hints

To view the console inside the playground window, use the View arrow Debug Area arrow Activate Console menu item.

Step-by-Step

  • Create a new Swift playground.
    1. Launch Xcode and create a new Swift playground by selecting the File arrow New arrow Playground menu item.
    2. In the playground options screen, use the following values:
      • Name: ClassPlayground
      • Platform: iOS
    3. Save the playground onto your hard disk.
      • Create the Calculator class.
    4. Delete the default contents of the playground file.
    5. Type the following lines:
      import UIKit
      class Calculator : NSObject
      {
          func AddTwoNumbers(firstNumber:Double, secondNumber:Double) -> Double {
              return firstNumber + secondNumber
          }
          func SubtractTwoNumbers(firstNumber:Double,
                                  secondNumber:Double) -> Double {
              return firstNumber - secondNumber
          }
          func MultiplyTwoNumbers(firstNumber:Double,
                                  secondNumber:Double) -> Double {
              return firstNumber * secondNumber
          }
          func DivideTwoNumbers(firstNumber:Double,
                                secondNumber:Double) -> Double? {
              if (secondNumber == 0) {
                  return nil
              }
              return firstNumber / secondNumber
          }
      }
  • Create an instance of the Calculator class and call some of its methods.
    1. Type the following lines after the end of the Calculator class definition:
      var arithmeticCalculator:Calculator = Calculator()
      let num1 = 17.5
      let num2 = 19.76
      let sum  = arithmeticCalculator.AddTwoNumbers(num1, secondNumber: num2)
      let difference  = arithmeticCalculator.SubtractTwoNumbers(num1,
      secondNumber: num2)
      let product  = arithmeticCalculator.MultiplyTwoNumbers(num1,
      secondNumber: num2)
      let division  = arithmeticCalculator.DivideTwoNumbers(num1, secondNumber:
      num2)
      print("\(num1) + \(num2) is \(sum)")
      print("\(num1) - \(num2) is \(difference)")
      print("\(num1) * \(num2) is \(product)")
      print("\(num1) / \(num2) is \(division!)")
    2. Observe the results of this program in the console. You should see the following output in the console:
      17.5 + 19.76 is 37.26
      17.5 - 19.76 is -2.26
      17.5 * 19.76 is 345.8
      17.5 / 19.76 is 0.885627530364372