I find a lot of confusion among beginners and a handful of intermediate developers about what is a class. Object oriented languages like Swift, Objective C or C++ use classes to organize everything in the language and the API’s. You might have heard words like callback, override, setter and getter and have no idea what they mean. Have you ever wonder what’s really going on in
viewDidLoad
? In this three-part lesson I’m going to explain what all this means.
We’ll use the playground to introduce classes. This will give us more immediate results to our coding. Open Xcode, and make a new playground named ClassPlayground.
Once in the playground, add the following:
// a basic class for a pizza class pizza{ }
This is a basic class. We also call classes objects. Almost everything we use to develop apps is a class, such as Array
, String
, UIColor
or UILabel
. If we think of a physical object, there is a way of describing the object: a description of the object and what it does. If I ordered a pizza, I might order a 10 inch wheat crust BBQ Chicken Pizza. A pizza would have a diameter, crust type, and several toppings. When discussing classes, we call these properties. Properties are variables and constants we use to describe the class. Change the pizza class to:
class Pizza{ // a basic class for a pizza //MARK: Properties var diameter:Double = 0.0 var crust:String = "" var toppings:[String] = [] }
Our pizza class now has three properties. First we have a Double
variable diameter
that will give us the diameter of the pizza. Secondly we have a string where we can describe either whole wheat or white crust. Finally we have a string array of toppings.
Under the class in the playground add this:
let myPizza = Pizza() let myFriendsPizza = Pizza()
By using our class name Pizza
with parentheses after it, we created two constant objects, myPizza
and myFriendsPizza
of class pizza. We call each of these objects an instance of Pizza
. If you look to the right of these statements, you’ll see that we have two blank instances:
Swift has allocated some memory and placed our blank default from our variable assignments in the object. This process is known as instantiation. Swift does not have to allocate the memory immediately. Sometimes as a good memory practice, we allocate the memory for an object only when we first use it, known as lazy instantiation. We only declare its existence with the assignment of the class. When we use a property, we allocate memory.
We can change our properties using dot notation. We give the object name, a dot and then the property. Add this code under the assignment:
myPizza.diameter = 10 //10 inch 25.4 cm pizza myPizza.crust = "White" myPizza.toppings = ["Chicken","BBQ Sauce","Cheese","Red Onions"]
You’ll notice we have set values to the properties of myPizza
.
Now do the same to myFriendsPizza
:
myFriendsPizza.diameter = 12 //12 inch 30.48cm pizza myFriendsPizza.crust = "Wheat" myFriendsPizza.toppings = ["Cheese","Pepperoni"]
That pizza has the values
We can also have the pizza tell us, or get, values. For example type this:
print(myPizza.diameter) print(myFriendsPizza.toppings)
We get this:
We printed the diameter of myPizza
and the toppings of myFriendsPizza
. For users, printing out the array is not very user-friendly. We might want to have the toppings print out in a more understandable form as a string. Add this to our Pizza
Class:
//MARK: Methods func toppingsString()->String{ var myString = "" for topping in toppings{ myString = myString + topping + " " } return myString }
This function loops through all the toppings in the array toppings
, storing them in an array myString
. It returns the string once done.
Functions found inside of classes are called methods. Methods are the actions the object can take. We converted an array to a string we could display in a UILabel
. Try out the new method by adding this to the bottom of the playground:
The method makes a string. Let’s add two more methods to our Pizza
class:
func area() -> Double{ return diameter * diameter * M_PI } func price(costPerSquareUnit:Double)-> Double{ return area() * costPerSquareUnit }
I do something silly here, but it works for our lesson. I figure out the price of the pizza by multiplying the area of the pizza by a cost per square unit. I’m working in inches and U.S. Dollars for my units. I figure a BBQ Chicken pizza might have a price of two cents per square inch and a sausage pizza might have a price of one and a half cents per square inch. This however requires an area calculation, so I made another method to calculate the area.
Test the area()
method like this:
Test the price function like this:
Subclassing and Overrides
Suppose the pizza restaurant also makes deep dish pizza. Since a deep dish pizza is thicker than the flat pizza we’ve defined, an area alone is not the best calculation of a pizza price. We are using a lot more ingredients to fill the volume. Instead of computing area, we need to compute volume and multiply that with costPerSquareUnit
. We could re-write the entire class, but we don’t have to. We need exactly the same properties as we did before, plus one more for the depth of the pan. Instead of rewriting our Pizza
class, we will subclass Pizza
, and make a new class DeepDishPizza
. At the bottom of your playground code add this:
class DeepDishPizza:Pizza{ }
We defined the class DeepDishPizza
and added after it a colon and the name of the class we subclassed. Now type this:
let annahsPizza = DeepDishPizza() annahsPizza.diameter = 8.0 annahsPizza.crust = "White" annahsPizza.toppings = ["Cheese","Sausage","Pepperoni"] annahsPizza.toppingsString() annahsPizza.area() annahsPizza.price(0.25)
Everything we defined in Pizza
works in DeepDishPizza
. We say that DeepDishPizza
inherits from Pizza
. Pizza
is the parent class or superclass of DeepDishPizza
. DeepDishPizza
is the child class or subclass of Pizza
. We say we subclassed Pizza
to make DeepDishPizza
.
We can also add some new things to the subclass. For example we can add a new property panDepth
class DeepDishPizza:Pizza{ var panDepth = 4 }
The child class has a property with a default pan depth of four inches. You have enough information to calculate the pizza volume. Add a new method to the DeepDishPizza
class:
func volume() -> Double{ return area() * panDepth }
We used the inherited class method area()
and multiplied that value by our panDepth
. Now try the new method:
annahsPizza.volume()
You’ll see the volume of the pizza.
We want to calculate price on a deep dish pizza by the volume not the area. We also want to use the same method price
that we used for the superclass in DeepDishPizza
for consistency sake. If you’ve worked enough with functions you know you can’t name two functions with the same identifier and parameters. The compiler would complain with a critical error. Subclasses, however, can override methods in a superclass. The keyword override
allows us to make a new definition for the method price.
Add this method to your
DeepDishPizza
class:
override func price(costPerSquareUnit: Double) -> Double { return volume() * costPerSquareUnit }
And now the price
method of DeepDishPizza
calculates for volume:
Sometimes, we don’t need to override a method. Instead we use different parameters. For example:
func price(costPerSquareUnit:Double, panDepth:Double) -> Double{ self.panDepth = panDepth return volume() * costPerSquareUnit }
In this method we set our panDepth
in the price. We have two parameters instead of one. Swift see this as a completely different function, and thus we don’t need the override
keyword.
Notice in this function the following line:
self.panDepth = panDepth
We’ll discuss more about scope in the next part, but our parameter name is the same as our property name. Swift assumes that the identifier refers to the last defined identifier of that name. panDepth
means the parameter panDepth,
not the property panDepth
. For us to tell Swift to we want the property panDepth
, we use the self
keyword. The keyword self
tells the compiler we are talking about a property or method in this class. self
means “this instance.” The statement above thus assigns the value of the parameter as the value of the property.
Class Methods
The methods we have been working with are instance methods. To use one, you define an instance of a class and refer to the instance to run the method. There is also class Methods, which need only the class name. The most common use for class methods is for initializing our properties when we create an instance. Often you’ll hear of such class methods as initializers or constructors. For example, add the following to our Pizza
class, just above the instance methods:
//MARK: Class Methods (initializers) init(diameter:Double, crust:String, toppings:[String]){ self.diameter = diameter self.crust = crust self.toppings = toppings }
This is a method, but we do not use func
to start it. We don’t even have a name for the method. Instead you use the keyword init
, followed by your parameter list. This code, like many constructors, assigns values to our properties. You’ll notice that you have a few errors in the playground. Once you define a class method, Swift’s automatic assumptions about initializing methods shuts down. Of course the bugs inherit like everything else in a subclass. We have an error for DeepDishPizza
too. When you start defining class methods, always make one method above all your other constructors named init
with no parameters:
init(){}
When you add this to the Pizza
class, the errors disappear. Now under myFriendsPizza
You can define darrylsPizza
.
let darrylsPizza = Pizza( diameter: 14, crust: "Wheat", toppings: ["Fresh Mozzarella","Tomatoes"])
and see that it sets properties correctly.
You can of course add class methods to subclasses, with one wrinkle. Add this to the DeepDishPizza
class:
//MARK: Class Methods override init(){ super.init() }
Here’s our parameterless init
. It has one line of code. In order to inherit from the superclass you have to call the superclass’s init
. If you don’t, you will get an error. The keyword super
gives us a reference to the superclass, where we can call its init
method.
We can add parameters to subclass’ class methods, but always initialize the superclass first. Here’s two examples:
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 }
In init(panDepth:Double)
we set the pan depth only, and leave the default values for the superclass. In the second case, we set all of our properties. Here we use super
to initialize the properties defined in the superclass with its initializer that has parameters, while setting child class property panDepth
directly.
Once added test these out, and we get this:
That’s enough to comprehend in one post. We’ll continue next week with part two. We’ll learn a little more about Scope, Setters and getters. After that, in part three We’ll learn about protocols and abstract classes.
The Whole Code
//: Playground - noun: a place where people can play //***************************************** // Classes Lesson 1 playground // By Steven Lipton, MakeAppPie.com // (C)2015 MakeAppPie.com //***************************************** import UIKit class Pizza{ // a basic class for a pizza //MARK: Properties var diameter:Double = 0.0 var crust:String = "" var toppings:[String] = [] //MARK: Class Methods 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 } } // Test initializers let myPizza = Pizza() let myFriendsPizza = Pizza() let darrylsPizza = Pizza( diameter: 14, crust: "Wheat", toppings: ["Fresh Mozzarella","Tomatoes"]) // Test the instance methods myPizza.diameter = 10 //10 inch 25.4 cm pizza myPizza.crust = "White" myPizza.toppings = ["Chicken","BBQ Sauce","Cheese","Red Onions"] myFriendsPizza.diameter = 12 //12 inch 30.48cm pizza myFriendsPizza.crust = "Wheat" myFriendsPizza.toppings = ["Cheese","Pepperoni"] // I placed these in print functions // In playgounds you don't have to do that, // since the values show up to the right print(myPizza.diameter) print(myFriendsPizza.toppings) print(myFriendsPizza.toppingsString()) print(myPizza.toppingsString()) print (myPizza.area()) print(myFriendsPizza.area()) print(myPizza.price(0.02)) print(myPizza.price(0.015)) //***************************** // 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 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 } } // Test Class methods let annahsPizza = DeepDishPizza() let jillsPizza = DeepDishPizza(panDepth: 3.0) let flosPizza = DeepDishPizza( diameter: 10, crust: "White", toppings: ["Cheese"], panDepth: 3.0) //test instance methods // didn't use print here annahsPizza.diameter = 8.0 annahsPizza.crust = "White" annahsPizza.toppings = ["Cheese","Sausage","Pepperoni"] annahsPizza.toppingsString() annahsPizza.area() annahsPizza.volume() annahsPizza.price(0.023)
Leave a Reply