A Swift Tutorial for Working with Classes Part 3: Abstract classes.

pizza classes

In the first tutorial in this series we introduced classes. In  the second we did some more advanced manipulations of classes. In both, we discussed the idea of inheritance, which has one final possibility: a class whose only purpose is to define other classes. This is known as  an abstract class.

Abstract classes are mere skeletons of their child classes. The abstract class gives some structure,  but no function.  The subclasses flesh out the differences between classes for different functionality.

In these lessons we’ve been using pizzas. All pizzas have a size, a crust and toppings.  We might want to know the area of almost any pizza.  How different types of pizzas use those  properties and method changes dramatically.  I charted out a few types of pizza. A flat round pizza is different from a rectangular pizza and a flatbread pizza. All of those are different from pan pizzas, where we aren’t interested in area, but volume.  All of those are  different from  Bowl pizzas. Yet, they all have toppings, size and crust. To make subclasses of our abstract pizza, we change how we define and use those three properties.

Lets look at all these pizzas in an example in the playground. If you have followed along with the previous lessons, we will be starting from scratch, though the code will look familiar. For those picking up this lesson, as long as you understand the concepts from the first two lessons you do not have to catch up in coding.

In Xcode, make a new playground named  AbstractPizzaPlayground.  Clear out what’s in the playground and add the following code.

import UIKit

class AbstractPizza { //our Abstract pizza Class
        let pizzaPi = 3.14
        var size:Double! = nil
        var crust:AnyObject! = nil
        var topping:[String]! = nil
    func area() -> Double! {
        return nil
    }
}

This is a simple class with the properties size, crust and topping. It has one constant pizzaPi for pi. It has one method for area. While that alone is not remarkable, everything is optional and set to nil. This is an empty and useless class by itself.  We made sure anything using this class knows how empty it is by making everything optional. You don’t have to make it optional, but it is a good practice. In Apple’s APIs like UIKit, you find a lot of things that are optional that don’t need to be. This is why – so you can tell easily if the class is empty and needing some populating.
We have an empty function area. The method area will return nil if you don’t implement it. In any subclass, you have to override the parent class’ implementation of the method to get it to work correctly for that subclass.

There’s one more common practice in abstract classes in Swift and in Objective-C. Change the code to the following:

class AbstractPizza { //our Abstract pizza Class
        let pizzaPi = 3.14
        var size:Double! = nil
        var crust:AnyObject! = nil
        var topping:[String]! = nil
    init(){ //use an empty method to initialize values
        pizzaDidLoad()
    }
    func area() -> Double! {
        return nil
    }
    func pizzaDidLoad(){
        //empty method for initialization
    }
}

We initialized the class manually by calling a method pizzaDidLoad, which here is empty. I named this like a very familiar method in UIKIt intentionally: viewDidLoad. All of the life cycle methods of a view controller get defined like this as empty methods. This is a Objective-C throwback, but still handy. Unlike Swift, in Objective-C you cannot initialize  with a value when declaring a property. This is how you initialize.  Upon initialization of a view controller, the code in the initializer will run the viewDidload method. If empty, it does nothing. If not, it does the preliminary steps for setting up the view controller by initializing some values. Its counterpart viewWillAppear does the same further along the presentation of a view controller.

Subclassing the Abstract Class: FlatPizza

Let’s add a subclass from the abstract class. Under the AbstractPizza code, add a flat pizza class like this:

// FlatPizza -- demonstrating the basic subclassing of a abstract class
class FlatPizza:AbstractPizza{ //flat round pizza
    //define area
    override func area() -> Double! {
        let radius = size / 2.0
        return radius * radius * pizzaPi
    }
    //set default values for properties
    override func pizzaDidLoad() {
        size = 10.0
        crust = "White"
        topping = ["Pizza Sauce","Cheese"]
    }
}

In FlatPizza, we inherited all the properties and methods from AbstractPizza. First we implemented the area method. Secondly, we set some default values in pizzaDidLoad. While this was required in Objective-C, It is not necessary in Swift, since we could do the same by initializing a variable or in a initializer. It still  comes in handy when you don’t want to   override init.

Test our code. Add this to the bottom of the playground and see the results:

2015-08-04_07-17-16

Using Optional Chaining: PanPizza

Next let’s add a Pan Pizza:

//PanPizza -- demonstrate adding a property and methods with optional chaining class
PanPizza:AbstractPizza{
    var depth = 0.0
    override func area() -> Double! {
        if let currentSize = size{
            let radius = currentSize / 2.0
            return radius * radius * pizzaPi
        }else{
            return nil
        }
    }
    func volume() -> Double!{
        if let area = area() {
            return area * depth
        }else{
            return nil
        }
     }
}

This time we did not initialize the properties with values. Instead, we use optional chaining to check if we have a value. Optional chaining assigns a constant to an optional value as a conditional statement. If the optional is nil, the statement is false.  If the statement is non-nil, the value gets assigned to the constant. In the volume function, we check if area is nil, if not we use the constant area to compute the volume from our added property for the depth of a pizza. If nil, we return nil.

The area method uses optional chaining to check the value of size. When using abstract methods, use optionals for the return values, so you can use checks such as optional chaining to decide if the method ever got written. Test out our pan pizza at the bottom of the playground:

2015-08-04_07-27-52

Overriding Properties … or Not: RectPizza

Let’s try overriding a property. We might have a pizza that is a rectangle. For that, we will need a size that has a length and a width, not a diameter. Add the following class to the playground:

class RectPizza:AbstractPizza{
    struct PizzaDimension{
        var height:Double = 0.0
        var width:Double = 0.0
    }
    var size:PizzaDimension = PizzaDimention()
}

We make a struct PizzaDimension to allow us to  have both a height and width, then try to change the type of size to the new type. This should work — but it doesn’t. We get an error:
Property 'size' with Type 'RectPizza.PizzaDimension' Cannot override a property with type 'Double'
You cannot override the type of  an inherited property. The easiest solution is make a new one. Change the class to this:

// Demonstring overriding properties, or not...
class RectPizza:AbstractPizza{
    struct PizzaDimension{
        var height:Double = 0.0
        var width:Double = 0.0
    }
    //this does not work -- we cant overrride type of a property
    //var size:PizzaDimension = PizzaDimension()
    //added property
    var sizeRect = PizzaDimension()
    override func area() -> Double! {
        return sizeRect.height * sizeRect.width
    }
    override func pizzaDidLoad() {
        sizeRect.height = 10.0
        sizeRect.width = 12.0
        crust = "Wheat"
        topping = ["Pizza Sauce","Cheese","Mushroom"]
    }
}

We added the property, then overrode the pizzaDidLoad and area. methods. Test this with

let rectanglePizza = RectPizza()
rectanglePizza.area()

and we get this.

2015-08-05_06-15-04

This is the simplest way of changing a property — make a new one. There is a more complex way in Swift, called generics, but that is a lesson for another time.

Subclassing a Subclass: FlatBreadPizza

flatbreadFlatbread pizzas are getting popular. These are a circle of dough flattened in one direction, to make a long rectangular shape with rounded ends, as you can see in the illustration. To find the area of these pizzas, we can think of them as a circle and a smaller rectangle added together. To make the class for this pizza, we do not use the abstract class, but subclass RectPizza, since every property we need is there already. Add this code:

class FlatBreadPizza:RectPizza{
    override func area() -> Double!{
        let rect = (sizeRect.height - sizeRect.width) * sizeRect.width //area with round parts removed to make a rectangle
        let radius = sizeRect.width / 2.0
        let circle = radius * radius * pizzaPi  // the circle that remains
        return rect + circle
    }
}

We can subclass any class, and often we may start with the abstract class as the base class, but subclass the child classes much like we did here. You don’t have to make everything from the abstract class.

AnyObject in Class Methods: BowlPizza

Another pizza is the bowl pizza or known by its inventors as the Pizza Pot Pie. Essentially you put all the pizza toppings in a bowl, put the pizza dough over the top of the bowl and bake this pizza. When done, you turn the bowl upside down and remove the bowl. We don’t need an area here. Like a pan pizza, we need a volume. We can estimate the volume by the half the volume of a sphere, which is a bowl. The formula is 2/3*pi*r^3 for half a sphere. If you’ve followed along, adding a BowlPizza class is pretty easy at this point. We want a class that looks like this:

class BowlPizza:AbstractPizza{
    func volume() -> Double!{
         if let bowlSize = size {
            let radius = bowlSize / 2.0
            return radius * radius * radius * pizzaPi * (2.0/3.0) //volume of half a sphere
        } else {
            return nil
        }
    }
}

That was simple enough. We have not added a class method to our abstract class. Let’s add the following to AbstractClass for personal pizzas:

class func personalPizza() -> AbstractPizza!{
        let pizza = AbstractPizza()
        pizza.size = 10.0
        pizza.topping = ["Pizza Sauce","Cheese"]
        pizza.crust = "White"
        return pizza
    }

This allows us to make an AbstractPizza from the class and for all other pizzas to inherit it. But there is a problem. Test the BowlPizza like this:

let bowlPizza = BowlPizza.personalPizza()
bowlPizza.volume

We get an error:
'AbstractPizza' does not have a member named 'volume'

There error says there is no method volume to call.  The volume method is in BowlPizza. The class method only returns AbstractPizza because we told it to, so it’s not a BowlPizza but an AbstractPizza. We need it to not return an AbstractPizza. This is what the special type AnyObject is for: it returns something, but does not tell us what type. In the class method change AbstractPizza to AnyObject!

class func personalPizza() -> AnyObject!{

Now in BowlPizza, override the class method

    override class func personalPizza() -> AnyObject! {
        let pizza = super.personalPizza() as! AbstractPizza
        let bowlPizza = BowlPizza()
        bowlPizza.size = pizza.size
        bowlPizza.topping = pizza.topping
        bowlPizza.crust = pizza.crust
        return bowlPizza
    }

We did not return a BowlPizza here, but an AnyObject!. We define our class method like this:

override class func personalPizza() -> AnyObject!

We might subclass this class and have the same problems we had with AbstractClass. Whenever we return our class, using AnyObject makes it easier for the subclass to deal with the override.

This approach does add some code. Swift cannot  guess what an AnyObject is. We have to downcast the AnyObject  with the as! operator to the class we need. For example in the code above

let pizza = super.personalPizza() as! AbstractPizza

In case you are wondering, we can’t downcast an AnyObject to a BowlPizza in this example. The AnyObject was an AbstractPizza instance, and not a BowlPizza. AnyObject and as! don’t change classes. AnyObject is a way to handle objects that might have type problems while sending them between other objects. In table views and collection views, the API returns cells by methods like  cellforRowAtIndexpath  as an AnyObject. This allows us to have custom cells and Swift does not care what we put in them until we need them, and then we downcast  with as!

Test our code by changing the test code to

let bowlPizza = BowlPizza.personalPizza() as! BowlPizza
bowlPizza.size
bowlPizza.volume()

Once again we downcast before we use the object. We have a size of 10, and a volume of 261.66

Protocols: FoodPriceProtocol

There are times we need abstraction not between parent and child classes but all classes. That is what protocols are for. At the top of your code, just below the import UIKit add this

protocol FoodPriceProtocol{
    price(pricePerUnit:Double) -> Double
}

This does nothing more than declaring there is a protocol called FoodPriceProtocol. If any class uses this protocol, it must implement the methods listed in the protocol. However, we implement the protocol in the context of the class. This makes for a function that will work the same way for every class, but give appropriate results.

We use the protocol by adopting it. To adopt it we add the protocol after the superclass, if there is one. If not, we declare it where the superclass usually goes.  Change FlatPizza‘s declaration to this:

class FlatPizza:AbstractPizza,FoodPriceProtocol{ //flat round pizza

The protocol will demand you add price to your class with the message.
Type 'FlatPizza' does not conform to protocol 'FoodPriceProtocol'
Add the following to the FlatPizza class

//protocol
    func price(pricePerUnit: Double) -> Double {
        return area() * pricePerUnit
    }

 

You can now test the protocol by typing at the bottom of the playground

2015-08-06_05-54-40

Adopt the protocol for the BowlPizza.

class BowlPizza:AbstractPizza,FoodPriceProtocol{

You don’t have to use the same formula for all adoptions of a protocol.  For example, Bowl pizzas only come in half or pound sizes, so change price to this.

  func price(pricePerUnit: Double) -> Double {
        if pricePerUnit == 1.0 {
            return 23.50
        } else if pricePerUnit == 0.5 {
            return 11.75
        } else {
            return 0
        }
    }

Now bowl pizzas will only give a price for a pound or half-pound bowl of two price per units, and otherwise return zero.

2015-08-06_05-59-56

Data Sources and Delegates: ChocolateChipCookie

We don’t even need a class based on AbstractPizza to use the protocol. Make the class ChocolateChipCookie with a price computed by the number of  of chocolate chips.

class ChocolateChipCookie:FoodPriceProtocol{
    var chipCount = 0
    func price(pricePerUnit: Double) -> Double {
        return price pricePerUnit * Double(chipCount)
    }
}

Data Sources

Because protocols are so easy to use in any class, there are two special uses for them: Data Sources and Delegates. A data source is protocol with a series of methods describing how to get and use some data. For example add this data source protocol above ChocolateChipCookie

protocol ChocolateChipCookieDataSource{
    func numberOfChipsPerCookie() -> Int
    func bagOfCookiesCount() -> Double
}

We added two values and methods. One, numberOfChipsPerCookie ,will be the amount of chocolate chips, essentially replacing the chipCount property. The other is how many cookies are in a bag of cookies. Change ChocolateChipCookie code to this

class ChocolateChipCookie:FoodPriceProtocol,ChocolateChipCookieDataSource{
    var chipCount = 0
    func price(pricePerUnit: Double) -> Double {
        //return price pricePerUnit * Double(chipCount)
        return pricePerUnit * Double(numberOfChipsPerCookie())
    }
    func numberOfChipsPerCookie() -> Int {
        return 15
    }
    func bagOfCookiesCount() -> Double{
        return 10
    }
}

We adopted the data source, and used the the required method from the protocol to have 15 chocolate chips per cookie and 10 cookies per bag. While we can’t force a developer to set data with a property, we can with a data source. Classes like UITableViewController use this to the fullest to control data going in and out. Many times the data in a data source may have a lot of conditional changes in each subclass, and the protocol makes it easy to write code to control the data. For example we can change bagOfCookiesCount to this:

func bagOfCookiesCount() -> Double {
        //return 10.0
        //get more cookies differnt days of the week
        let now = NSDate.timeIntervalSinceReferenceDate()
        let dayInSeconds = 86400.0
        let day = Int(now / dayInSeconds) % 7
        return Double(day) + 5.0 //five plus however many cookies for the day
    }

Now the number of cookies changes depending on the day of the week. While this is a contrived example, many times our data will change due to conditions of our app. For a real example, check out the continuous picker view tutorial. In that example we mess with the indexes of an array in the pickerView:titleforRow:forComponent method of the data source to make the picker view seemingly loop forever.

Delegates

A delegate is a series of methods used in one class but available for any class to use the original class functionality. The protocol FoodPriceProtocol is actually a delegate. We could write another method for the cookie like this:

func bagOfCookiesPrice(cookieCount:Double) -> Double{
    return price(0.05) * cookieCount
}

This is where delegates find their use.  They make methods when that will be common among a bunch of classes but defined differently each time. We know  they will return a certain type. The function price will return a double giving us a price. I know any class that implements price will return a Double. I can add this method to ChocolateChipCookie:

func bagOfCookiesPrice() -> Double{
    let pricePerUnit = 0.05
    let pizza = FlatPizza()
    let discount = (pizza.price(pricePerUnit) * 0.10)
    return discount * price(pricePerUnit) * bagOfCookiesCount()
    }

We make the price of a bag of cookies have a discount of 10% of a price of a default flat pizza. In discount we used price with pizza, and returned price of the cookie multiplied by the discount and quantity. We used  price on two different classes that really don’t have anything to do with each other.

This can get sophisticated. The most important use of a delegate is delegation between view controllers. This allows us to move values from a destination view controller back to its calling controller. I’ve written two posts on that subject. Take a look at Why Do We Need Delegates and Using Segues and Delegates in Navigation controllers for more on this.

Classes are the core of object oriented programming in languages like Swift. Knowing how to use methods and properties, how to subclass and use abstract methods make for a well structured easy to read and debug app. Steve Jobs gave the best metaphor for using classes properly: Your code should be as modular as Lego building blocks: they just fit together.

The Whole Code

Since WordPress won’t let me add code files directly, I had to make the download of the code a .docx file. Open AbstractPizzaDemo in Microsoft Word or a compatible word processor and cut and paste the source code into the playground if you wish. Though at that point, you could cut and paste the code below into a blank playground.

//The AbstractPizza example 8/4/2015  makeapppie.com

import UIKit
// A simple protocol, actually a delegate
protocol FoodPriceProtocol{
    func price(pricePerUnit:Double) -> Double
}

//The abstract pizza base class
class AbstractPizza { //our Abstract pizza Class
        let pizzaPi = 3.14
        var size:Double! = nil  // Nil helps indicate empty
        var crust:AnyObject! = nil
        var topping:[String]! = nil
    init(){ //use an empty method to initialize values
        pizzaDidLoad()
    }
    class func personalPizza() -> AnyObject!{  //AnyObject helps subclass
        let pizza = AbstractPizza()
        pizza.size = 10.0
        pizza.topping = ["Pizza Sauce","Cheese"]
        pizza.crust = "White"
        return pizza
    }

    func area() -> Double! {  //Abstract functions often return nil
        return nil            // to indicate empty
    }
    func pizzaDidLoad(){
        //empty method for initialization
    }
}

// FlatPizza -- demonstrating the basic subclassing of a abstract class
//Also demonstrates protocol adoption
class FlatPizza:AbstractPizza,FoodPriceProtocol{ //flat round pizza
    //define area
    override func area() -> Double! {
        let radius = size / 2.0
        return radius * radius * pizzaPi
    }
    //set default values for properties
    override func pizzaDidLoad() {
        size = 10.0
        crust = "White"
        topping = ["Pizza Sauce","Cheese"]
    }
    //protocol
    func price(pricePerUnit: Double) -> Double {
        return area() * pricePerUnit
    }
}

//PanPizza -- demonstrate adding a property and methods with optional chaining
class PanPizza:AbstractPizza{
    var depth = 0.0
    override func area() -> Double! {
        if let currentSize = size{
        let radius = currentSize / 2.0
            return radius * radius * pizzaPi
        }else{
            return nil
        }
    }
    func volume() -> Double!{
        if let area = area() {
            return area * depth
        }else{
            return nil
        }
    }
}
// Demonstring overriding properties, or not...
class RectPizza:AbstractPizza{
    struct PizzaDimension{ //a struct for the height and width of a rectangle
        var height:Double = 0.0
        var width:Double = 0.0
    }
    //this does not work -- we cant overrride type of a property
    //var size:PizzaDimension = PizzaDimension()
    //added property sizeRect
    var sizeRect = PizzaDimension()
    override func area() -> Double! {
        return sizeRect.height * sizeRect.width
    }
    override func pizzaDidLoad() {
        sizeRect.height = 10.0
        sizeRect.width = 12.0
        crust = "Wheat"
        topping = ["Pizza Sauce","Cheese","Mushroom"]
    }
}
//Subclassing a subclass that used an abstract class
class FlatBreadPizza:RectPizza{
    override func area() -> Double!{
        let rect = (sizeRect.height - sizeRect.width) * sizeRect.width //area with round parts removed to make a rectangle
        let radius = sizeRect.width / 2.0
        let circle = radius * radius * pizzaPi
        return rect + circle
    }
}
//Demonstrating class methods and AnyObject!
//Also Demonstrates protocol adoption
class BowlPizza:AbstractPizza,FoodPriceProtocol{ //protocol adoption
    override class func personalPizza() -> AnyObject! {
        let pizza = super.personalPizza() as! AbstractPizza
        let bowlPizza = BowlPizza()
        bowlPizza.size = pizza.size
        bowlPizza.topping = pizza.topping
        bowlPizza.crust = pizza.crust
        return bowlPizza
    }

    func volume() -> Double!{
        if let bowlSize = size {
            let radius = bowlSize / 2.0
            return radius * radius * radius * pizzaPi * (2.0/3.0) //volume of half a sphere
        } else {
            return nil
        }
    }
    //Protocols (delegates and Data sources)
    func price(pricePerUnit: Double) -> Double {
        if pricePerUnit == 1.0 {
            return 23.50
        } else if pricePerUnit == 0.5 {
            return 11.75
        } else {
            return 0
        }
    }
}
//An example of a data source
protocol ChocolateChipCookieDataSource{
    func numberOfChipsPerCookie() -> Int
    func bagOfCookiesCount() -> Double
}

//examples of delegates and data sources with a different class
// no superclass here, so the protocols goes directly after the class declaration
// If there is a superclass, like aboove, the superclass goes first in the list.

class ChocolateChipCookie:FoodPriceProtocol,ChocolateChipCookieDataSource{
    var chipCount = 0
    func price(pricePerUnit: Double) -> Double {
        //return price pricePerUnit * Double(chipCount)
        return pricePerUnit * Double(numberOfChipsPerCookie())
    }
    func numberOfChipsPerCookie() -> Int { //simple datasource which returns a value
        return 15
    }
    func bagOfCookiesCount() -> Double { //datasource whihc calcualtes its value
        //return 10.0
        //get more cookies differnt days of the week
        let now = NSDate.timeIntervalSinceReferenceDate() //get the number of seconds from refrence date to now
        let dayInSeconds:Double = 60 * 60 * 24 //convert to days
        let day = Int(now / dayInSeconds) % 7 // convert to day of week as 0 - 6
        return Double(day) + 5.0 //five plus however many cookies for the day
    }
    func bagOfCookiesPrice(cookieCount:Double) -> Double{ //using the protocol method
        return price(0.05) * cookieCount
    }
    func bagOfCookiesPrice() -> Double{ //using the protocol method in two different classes
        let pricePerUnit = 0.05
        let pizza = FlatPizza()
        let discount = (pizza.price(pricePerUnit) * 0.10)
        return discount * price(pricePerUnit) * bagOfCookiesCount()
    }
}

// test area
let flatRoundPizza = FlatPizza()
flatRoundPizza.area()
flatRoundPizza.price(0.05)

let panPizza = PanPizza()
panPizza.size = 10.0
panPizza.depth = 2.0
panPizza.volume()

let rectanglePizza = RectPizza()
rectanglePizza.area()

let bowlPizza = BowlPizza.personalPizza() as! BowlPizza
bowlPizza.size
bowlPizza.volume()
bowlPizza.price(1.0)
bowlPizza.price(0.5)
bowlPizza.price(1.1)

let cookie = ChocolateChipCookie()
cookie.price(0.05)
cookie.bagOfCookiesPrice(5)

8 Replies to “A Swift Tutorial for Working with Classes Part 3: Abstract classes.”

    1. You are welcome. Glad it was a help. Besides my generally being obsessed with pizza, food in general, and one of the most loved foods specifically makes for a easy to grasp concept.

      I will be expanding this series in a couple of weeks as an e-book with a few more tips and techniques.

  1. Thank you! I’m a complete newbie and can totally follow the pizza analogy but am having trouble applying it to my own project.

    I am trying to make a list of makes of cars and then have different number of models depending on the make of the cars and then for each model having 2 more unique attributes.

    I made a class called Makes which had a name variable and a subclass called Models which had a name, year and colour variable. As some makes have more than one model, I couldn’t work out how to add it to my array so that ultimately I could create a tableview of Makes that segues to a tableview of Models that segues to a detailed view of the year/colour.

    Any suggestions welcome! And thanks for your tutorials so far!

    1. hmm.. should have made that clearer. Models should not be a subclass. make it a separate class and models would be a property of Makes. so

      Class Models{
      var name =""
      var year = 1950
      var color =""
      }
      
      Class Makes{
      var model = [Models]() //more than one model for the make
      var name = ""
      }

      in your view controller you would have a array of Makes

      var cars = [Makes]()

      I left out all the initializers, but you probably will need a few of those to populate you objects. in your first table view your selected object is the model of car, the. in your next tableview, your selected object is the make of car. the final selected object is the details about the car.

      1. Thank you! I think I just overloaded my brain so couldn’t think it through properly. Thanks again for your tutorials!

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