A Swift Guide to Working with Classes Part Two: Advanced Class Techniques

In our first part we looked at some of the basics of classes in Swift. In this part, we look at some of the more advanced features of classes which developers use every day — often without knowing it. We’ll explore first a bit more on class methods, scope, keeping others from messing with your properties and methods,  property observers and  computed properties

Set up the Playground

We’ll use the same playground we used in part one. I’ve created a zip file with three versions of the playground. One is the completed part one, the second is a cleaned up part one to use with this lesson, and the third is the completed part two. If you are in the mood to type or don’t want to download anything , you can create a new playground and add the following code to get started:

//: Playground - noun: a place where people can play

// Part two of the classes lesson
// This file is a cleaned up version of part one.
// You may use this or part one for your lesson. 

import UIKit
//**************************
// a basic class for a pizza
//***************************

class Pizza{

    //MARK: Properties
    var diameter:Double = 0.0
    var crust:String = ""
    var toppings:[String] = []

    //MARK: Class Methods -- Constructors

    init(){}
    init(diameter:Double, crust:String, toppings:[String]){
        self.diameter = diameter
        self.crust = crust
        self.toppings = toppings
    }

    //MARK: Methods
    func toppingsString()->String{
        var myString = ""
        for topping in toppings{
            myString = myString + topping + " "
        }
        return myString
    }
    func area() -> Double{
        return diameter * diameter * M_PI
    }
    func price(costPerSquareUnit:Double)->Double{
        return self.area() * costPerSquareUnit
    }

}

//*****************************
// The subclass DeepDishPizza
//*****************************

class DeepDishPizza:Pizza{
    // A subclass of Pizza with a pan depth.
    //price is computed by volume
    //MARK: Properties
    var panDepth:Double = 4.0
    //MARK: Class Methods -- Constructors
    override init(){
        super.init()
    }

    init(panDepth:Double){
        super.init()
        self.panDepth = panDepth
    }

    init(diameter: Double, crust: String, toppings: [String], panDepth:Double) {
        super.init(diameter: diameter, crust: crust, toppings: toppings)
        self.panDepth = panDepth
    }

    //MARK: Instance Methods
    func volume() -> Double{
        return area() * panDepth
    }

    override func price(costPerSquareUnit: Double) -> Double {
        return volume() * costPerSquareUnit
    }

    func price(costPerSquareUnit:Double, panDepth:Double) -> Double{
        self.panDepth = panDepth
        return volume() * costPerSquareUnit
    }
}

More on Class Methods: Type Methods

Lat time we worked with Swift’s constructors to initialize an object. Some languages use the same syntax for  constructors and other class methods. Swift has separate syntax for  constructors and class methods, which Apple refers to as Type Methods. These methods, like constructors, do not need an instance to run. They have two big uses: return something related to the method and preset initialized instances of the class. One place you see this used often is the UIKit class UIColor. While UIColor has several constructors for making colors, often we just want black, orange, yellow or blue. UIColor comes with several type methods to do this:

//UIColor
UIColor.blackColor()
UIColor.orangeColor()
UIColor.yellowColor()
UIColor.blueColor()

To make a class method is not much different from an instance method. For example, to the Pizza method add this code:

//MARK: Class Methods -- Type Methods
    class func pizzaIcon() -> String{ //return something related to the class
        return "🍕"
    }

We might need the pizza emoji at times, which is not easy to get to. If you do not know how to get it in Xcode,  press Control-Command-Spacebar to get the symbols menu. Navigate to the Emoji>Food section, and double-click a pizza. Just in making this class method you see how handy such a method can be. Note the only difference in this method from an instance method.  We use the keyword class in front of the method declaration func to declare it a class method. Try to keep type methods together in code, so set up a //MARK: to document in XCode.

At the bottom of the playground, try out the method:

Pizza.pizzaIcon()

And a pizza  emoji  shows in response. Type methods use the class name instead of the instance name.

Suppose we make a lot of personal cheese pizzas, or we start many personal pizzas with a cheese pizza. We can write a class method which creates one those pizzas, saving us a lot of work, just like UIColor.blackColor(). Add this under the pizzaIcon method :

class func personalCheese() -> Pizza{ //use as a frequently used pizza
        return Pizza(
            diameter: 10.0,
            crust: "White",
            toppings: ["Cheese","Marinara"])
    }

At the bottom of the playground,  try out the method:

let cheesePizza = Pizza.personalCheese()

Our basic pizza shows up as an object. However, this has a big problem: it inherits, but the subclasses get confused. Try this as your last line of code in the playground:

let deepDishPizza = DeepDishPizza.personalCheese()
deepDishPizza.panDepth

You get an error:
'Pizza' does not have a member named 'panDepth'
While Xcode allows you to use the type method personalCheese here, it makes it type Pizza, not type DeepDishPizza The type method returns a type Pizza object. One way of dealing with this is override the class. Add this to the DeepDishPizza class:

//MARK: Type Methods
override class func personalCheese() -> DeepDishPizza{
    let deepDish = DeepDishPizza(panDepth: 2.0) //Type DeepDishPizza
    let flat = Pizza.personalCheese() //Type Pizza
   //transfer from flat to deep dish properties we need
    deepDish.diameter = flat.diameter
    deepDish.crust = flat.crust
   deepDish.toppings = flat.toppings
   return deepDish
    }

The override will return a DeepDishPizza. It first makes a deep dish and a personal cheese pizza. Then we transfer the properties of a flat pizza to the deep dish. This keeps the personal pizzas consistent. Any time we change Pizza, DeepDishPizza will change with it. Then we return the deep dish pizza. Now delete the last two test lines and type them in again to reset the playground and run the new code.

let deepDishPizza = DeepDishPizza.personalCheese()
deepDishPizza.panDepth

The error disappears, and we get our panDepth for the deepDishPizza.

Scope for Classes: self and super

In the last post I mentioned self and super. Let’s look at that code a little more.  These two keywords are ways of explicitly referencing a class within a class. For example we have this function in DeepDishPizza from last time

    func price(costPerSquareUnit:Double, panDepth:Double) -> Double{
        self.panDepth = panDepth
        return volume() * costPerSquareUnit
    }

The function has a parameter named panDepth. We also have a property panDepth.  A function or code block indicated by braces {} always thinks as local as possible. The identifier panDepth is the parameter panDepth as far as the price method is concerned, not the property. We want to assign the parameter to the property. We reference the property by the keyword self, which means “this class instance” The code self.panDepth = panDepth means assign to the property value the value of the  parameter.

In the old days of Objective-C, every method and property in the class required a reference of self when used in the class. Swift assumes a lot more about your code. If you leave off self it assumes the local scope, which is usually the class. You don’t normally need to use self. In a few cases like conflicting identifiers and closures, it cannot figure that out, and you need to specify self.

We also had a constructor in DeepDishPizza that looked like this:

    init(panDepth:Double){
        super.init()
        self.panDepth = panDepth
    }

The second line is super.init(). This initializes everything that was in the parent class before we initialize the properties of the child class. We have two constructors named init(), one in DeepDishPizza and one in PizzaDeepDishPizza inherits the init method from the parent class Pizza but also assumes scope is local, so init() refers the  init() in DeepDishPizza. The keyword super tells the compiler that we want the inherited init() from the superclass Pizza.

You rarely use super. Most cases are when a class and the subclass use the same identifier. Most common is in init() or other constructors. Another place it can show up is initializing abstract functions like viewDidLoad. We’ll discuss those in our next lesson.

Public and Private

We know how to access properties and methods of our parent. We know how to access properties and methods in instances of a class and in class methods. Up to this point in our lesson, we’ve assumed every property and method is public and every property and methods are accessible. In Swift the properties and methods are by default public to the target it is in. If you have two targets, say a WatchOS app and an iOS app, they cannot see each other.  Having stuff public is not always  a good idea. If we do some calculations, we do not want the middle steps of the calculation to be accessible by other classes or other developers.  We want them invisible and inaccessible to anything outside the class or subclass. It keeps from having to search for bugs due to messing with an intermediate calculation.

Instead of using a highly accurate value for pi, let’s use the less accurate 3.14. Add the following to your code for the Pizza class:

private let pi = 3.14 

The keyword private keeps pi hidden from the outside. What we mean for “outside” is different in Swift than most languages. In most computer languages private or its equivalent makes the property private to the class. In Swift it makes it private the source code file. Unfortunately, using a Apple Playground means everything is in the same source file so we can’t test this. If we could, we’d find that when we made an instance of Pizza, we could not do the following without an error:

cheesePizza.pi

It’s a good  idea to keep calculations private. Add another private property to Pizza:

private var pizzaArea = 0.0

Change the method area to this:

private func area(){
   pizzaArea =  diameter * diameter * pi
}

What did this do? First we made a private property pizzaArea. We then changed the area method to be private, using our custom value of pi. Instead of returning a value it assigns it value to the property pizzaArea. By making the property and method private, we hide it from anything but the class and subclasses.

There were functions which used our area method which we’ll have to modify for this change. In Pizza change this:

func price(costPerSquareUnit:Double)->Double{
    area()
    return pizzaArea * costPerSquareUnit
}

Do the same for DeepDishPizza‘s volume method:

    func volume() -> Double{
        area()
        return pizzaArea * panDepth
    }

Inheritance means pizzaArea does exist in the subclass DeepDishPizza. So does the changes in the definition of area, so we made change in both places. Test out the code by using this:

cheesePizza.price(0.015)
cheesePizza.pizzaArea

deepDishPizza.price(0.015)
deepDishPizza.volume()

and we get results we like:

2015-07-28_07-39-49

Setters and Getters

Swift does many things automatically some other computer languages need a lot of work to do. One of the most important is setters and getters. Setters are methods where we assign a value to a property. Getters are methods where we get the value from the property. In Swift, most times we use the assignment operator = to do this.

We can use custom setters and getters in Swift, often for computing values or performing logic when a property changes. Note how clunky having to add the area() was for updating the property pizzaArea. It would be handy to make the getter for the pizzaArea do the work for us.

Let’s change area again to return a value:

     private func area() -> Double {
        return diameter * diameter * pi
    }

Now change the property pizzaArea from this

private var pizzaArea = 0.0

to this

/* version 2 of pizzaArea -- Computed property */
private var pizzaArea:Double {
        get{
           return area()
        }
    }

We remove the assignment operator and default value. Since we cannot infer the type, we add a type declaration. Then we have a block of code. In that block, we have a small function-like block of code starting with the keyword get. For a getter, it must include a return of a value.

Since we are computing here, we have two redundant calls to area. They don’t do anything now. You can comment them out, delete them or just ignore them. In real code if we used this getter, often referred to as a Computed Property, we would not code them at all.

If you look down at our test statements nothing has changed, which is what we want.

2015-07-28_07-39-49

Usually we don’t use setters as much as Property Observers. Property Observers are a special type of setter that changes some other value or does some logic when a given property changes.
You might have noticed we made a math mistake. Area is not computed by the square of the diameter, but the radius. Let’s add a new private property radius. When we set the diameter, we’ll divide the diameter by two to get the radius.

Change the line

var diameter = 0.0

to this

    private var radius = 0.0
    var diameter = 0.0{  //property observer
        didSet{
            radius = diameter / 2.0
        }
    }

When diameter changes, radius changes. The above code is the way to change a value after a property changes using didSet without a parameter. It will use the property name as the set value. We can add a parameter as well. The same code can be written:

/* didSet with a parameter */
    var diameter = 0.0{  //property observer after setting
        didSet(newDiameter){
        //old value will be stored in oldValue
            radius = newDiameter / 2.0
        }
    }

If you want to do something before the property is set, you can use willSet. We can write the same code to compute the radius with willSet this way:

var diameter = 0.0{  //property observer before setting
        willSet{
            //newValue is value it will be set to
            //diameter is value before setting
            radius =  newValue / 2.0
        }
    }

In any of these cases, it’s important to remember the observer does nothing until the value changes. That means in initialization or assigning default values, these do nothing. Test the method with this:

let newPizza = Pizza(diameter: 10, crust: "White", toppings: ["Cheese"])
newPizza.diameter = 12.0

Since we used an initializer, you see in the results pane this:

2015-07-28_10-36-24

At first, the diameter is 10 but the radius is 0. We initialized diameter, so the property observer didn’t fire.  When we change the diameter to 12, then the radius becomes 6. We don’t have that problem in the class method since it assigns the value 10 to the object:

let newerPizza = Pizza.personalCheese()

This code returns a radius of 5. We’ve  got one more thing to do to make our prices accurate: use the radius.  In Pizza, change the area method to

private func area() -> Double{
return radius * radius * pi
}

Our areas, volumes and prices change:

2015-07-28_10-13-59

I didn’t plan to make this more than one lesson, but apparently there is enough for three lessons. In our last lesson (I promise!!!) , we’ll explore the ways we can make an abstract class, one that is a mere skeleton, but can be flexible enough to make a lot of classes. We’ll discuss empty methods, protocols, delegates and data sources.

The Whole Code

You can download the zip file here:ClassesPlayground

You can also refer to this full version of the playground used. I commented out versions of methods as I changed them in the text above.


//: Playground - noun: a place where people can play

// Part two of the classes lesson
// This file is a cleaned up version of part one.
// You may use this or part one for your lesson. 

print("Hello Pizza!")

import UIKit
//**************************
// a basic class for a pizza
//***************************
class Pizza{

    //MARK: Properties
    private var radius = 0.0
    /* didSet with a parameter
    var diameter = 0.0{  //property observer after setting
        didSet(newDiameter){
        //old value will be stored in oldValue
            radius = newDiameter / 2.0
        }
    }
    */
    /* didSet using a parameter for new value

        var diameter = 0.0{  //property observer after setting
            didSet(newDiameter){
                radius = newDiameter / 2.0
            }
        }
    */
    /* will set without parameter */
    var diameter = 0.0{  //property observer before setting
        willSet{
            //newValue is value it will be set to
            //diameter is value before setting
            radius =  newValue / 2.0
        }
    }

    var crust:String = ""
    var toppings:[String] = []
    /* version 1 of pizzaArea
    private var pizzaArea = 0.0 */
    /* version 2 of pizzaArea -- Computed property */
    private var pizzaArea:Double {
        get{
            return area()
        }
    }
    private let pi = 3.14

    //MARK: Class Methods -- Constructors

    init(){

    }
    init(diameter:Double, crust:String, toppings:[String]){
        self.diameter = diameter
        self.crust = crust
        self.toppings = toppings
    }

    //MARK: Class Methods -- Type Methods
    class func pizzaIcon() -> String{ //return something related to the class
        return "🍕"
    }

    class func personalCheese() -> Pizza { //use as a special constructor
        // This works in all cases, including property observers.
        let aPizza = Pizza()
        aPizza.toppings = ["Cheese","Marinara"]
        aPizza.crust = "White"
        aPizza.diameter = 10.0
        return aPizza
    }

    //MARK: Methods
    func toppingsString()->String{
        var myString = ""
        for topping in toppings{
            myString = myString + topping + " "
        }
        return myString
    }
    /* Version one of area()  -- Starting
     private func area() -> Double {
        return diameter * diameter * M_PI
    }
    */
    /* Version two of area() -- Adding private property
    private func area() {
        pizzaArea =  diameter * diameter * pi
    }*/

    /* Version three of area() -- Private methods
    private func area() -> Double {
        return diameter * diameter * pi
    }*/
    /*version four of Diameter  -- Property observers*/
    private func area() -> Double{
        return radius * radius * pi
    }

    func price(costPerSquareUnit:Double) -> Double{
        area()  //does nothing without assignment
        return pizzaArea * costPerSquareUnit
    }
}

//*****************************
// The subclass DeepDishPizza
//*****************************

class DeepDishPizza:Pizza{
    // A subclass of Pizza with a pan depth.
    //price is computed by volume
    //MARK: Properties
    var panDepth:Double = 4.0
    //MARK: Class Methods -- Constructors
    override init(){
        super.init()
    }

    init(panDepth:Double){
        super.init()
        self.panDepth = panDepth
    }

    init(diameter: Double, crust: String, toppings: [String], panDepth:Double) {
        super.init(diameter: diameter, crust: crust, toppings: toppings)
        self.panDepth = panDepth
        self.diameter = diameter
    }
    //MARK: Type Methods
    override class func personalCheese() -> DeepDishPizza{
        let deepDish = DeepDishPizza(panDepth: 2.0) //Type DeepDishPizza
        let flat = Pizza.personalCheese() //Type Pizza
        //transfer from flat to deep dish properties we need
        deepDish.diameter = flat.diameter
        deepDish.crust = flat.crust
        deepDish.toppings = flat.toppings
        return deepDish
    }
    //MARK: Instance Methods
    func volume() -> Double{
        area()  //does nothing without assignment
        return pizzaArea * panDepth
    }

    override func price(costPerSquareUnit: Double) -> Double {
        return volume() * costPerSquareUnit
    }

    func price(costPerSquareUnit:Double, panDepth:Double) -> Double{
        self.panDepth = panDepth
        return volume() * costPerSquareUnit
    }
}

//Test statements
// UIColor Type (class) methods
UIColor.blackColor()
UIColor.orangeColor()
UIColor.yellowColor()
UIColor.blueColor()

//Test class (type) methods
Pizza.pizzaIcon()

//Test class methods to make a personal cheese
let cheesePizza = Pizza.personalCheese()
let deepDishPizza = DeepDishPizza.personalCheese()
deepDishPizza.panDepth = 3.0

//Test for self -- no test

//Test private methods -- but they are public in the same file.
cheesePizza.pi

cheesePizza.price(0.015)
cheesePizza.pizzaArea

deepDishPizza.price(0.015)
deepDishPizza.volume()

//Test methods for getter(Computed property) should not change the code above

//Test methods for setters(Property observers)

//initialization and assignment of property observers
let newPizza = Pizza(diameter: 10, crust: "White", toppings: ["Cheese"])
newPizza.diameter = 12.0
let newerPizza = Pizza.personalCheese()

//using the property observer
newPizza.price(0.015)

7 thoughts on “A Swift Guide to Working with Classes Part Two: Advanced Class Techniques”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s