Classes and inheritance
Reference types for things that should be shared: CS61A’s bank account as a class, identity with ===, type properties, subclasses that override behavior, and object lifetimes.
- CS61A
- Classes · Inheritance · Composing Programs 2.5–2.7
- Swift book
- Structures and Classes · Inheritance · Initialization · Automatic Reference Counting
A bank account is not a value to be copied. If two people share an account, a deposit by one must be visible to the other. Things like that, with an identity and a state that changes over time, are objects, and Swift models them with classes.
Defining a class
CS61A’s example is the bank account. A class looks much like a structure:
class Account {
let holder: String
var balance = 0
init(holder: String) {
self.holder = holder
}
func deposit(_ amount: Int) -> Int {
balance += amount
return balance
}
func withdraw(_ amount: Int) -> Int {
if amount > balance {
print("Insufficient funds")
return balance
}
balance -= amount
return balance
}
}
let account = Account(holder: "Ada")
print(account.deposit(100)) // 100
print(account.withdraw(30)) // 70
Two differences from a structure show up already. A class has no automatic
memberwise initializer, so init must set every property that has no
default. And deposit changes balance without being marked mutating,
even though account was bound with let.
Reference semantics
That second difference is the important one. A class is a reference type: a variable holds a reference to an object, and assigning it copies the reference, not the object.
What would Swift print?
class Account {
var balance = 0
}
let mine = Account()
let ours = mine
ours.balance += 10
print(mine.balance)Show answer and explanation
Answer10
mine and ours refer to the same object, so a change made through ours
is visible through mine. This is Python’s aliasing, on purpose. The let
means the reference cannot change; the object it refers to still can.
=== asks whether two references point to the same object. == is not
available unless the class defines what equality means:
class Account {
var balance = 0
}
let a = Account()
let b = a
let c = Account()
print(a === b, a === c) // true false
c is a different account that happens to have the same balance. Identity
and equality are different questions, and with classes you need to know
which one you are asking.
Type properties
A property marked static belongs to the class itself, not to any one
object. CS61A uses a class attribute for the interest rate shared by every
account:
class Account {
static let interestRate = 0.02
var balance = 0
}
print(Account.interestRate) // 0.02
Structures and enumerations can have static properties too.
Inheritance
A subclass is a class defined in terms of another, its superclass. It gets all of the superclass’s properties and methods, and can override some of them. A checking account is an account that charges a fee for withdrawals:
class Account {
var balance = 0
func deposit(_ amount: Int) -> Int {
balance += amount
return balance
}
func withdraw(_ amount: Int) -> Int {
if amount > balance {
print("Insufficient funds")
return balance
}
balance -= amount
return balance
}
}
class CheckingAccount: Account {
static let withdrawFee = 1
override func withdraw(_ amount: Int) -> Int {
super.withdraw(amount + CheckingAccount.withdrawFee)
}
}
let checking = CheckingAccount()
_ = checking.deposit(20)
print(checking.withdraw(5)) // 14
override is required, so you cannot replace a method by accident.
super.withdraw calls the superclass’s version, reusing its logic rather
than copying it.
What would Swift print?
class Account {
var balance = 0
func withdraw(_ amount: Int) -> Int {
if amount > balance {
print("Insufficient funds")
return balance
}
balance -= amount
return balance
}
}
class CheckingAccount: Account {
override func withdraw(_ amount: Int) -> Int {
super.withdraw(amount + 1)
}
}
let checking = CheckingAccount()
checking.balance = 20
print(checking.withdraw(5), checking.withdraw(100))Show answer and explanation
AnswerInsufficient funds\n14 14
Both arguments to print are evaluated before anything is printed. The
first withdrawal takes 5 plus the fee, leaving 14. The second asks for 101,
which prints “Insufficient funds” immediately and returns 14 unchanged.
Only then does print write 14 14.
A variable of the superclass type can hold a subclass object, and calling a method runs the version for the object’s actual class:
class Account {
var balance = 0
func withdraw(_ amount: Int) -> Int { balance -= amount; return balance }
}
class CheckingAccount: Account {
override func withdraw(_ amount: Int) -> Int { super.withdraw(amount + 1) }
}
let accounts: [Account] = [Account(), CheckingAccount()]
for account in accounts {
account.balance = 10
print(account.withdraw(3)) // 7, then 6
}
Put the lines in order
Put the lines in order so the program prints 14.
var balance = 0}func withdraw(_ amount: Int) -> Int {override func withdraw(_ amount: Int) -> Int {print(checking.withdraw(5))let checking = CheckingAccount()super.withdraw(amount + 1)}return balance}balance -= amountclass CheckingAccount: Account {checking.balance = 20}class Account {
Show the correct program
class Account {
var balance = 0
func withdraw(_ amount: Int) -> Int {
balance -= amount
return balance
}
}
class CheckingAccount: Account {
override func withdraw(_ amount: Int) -> Int {
super.withdraw(amount + 1)
}
}
let checking = CheckingAccount()
checking.balance = 20
print(checking.withdraw(5))Object lifetimes
Swift frees an object automatically when nothing refers to it any more, using automatic reference counting (ARC). A class can run code at that moment in a deinitializer:
class Tracker {
let name: String
init(_ name: String) {
self.name = name
print("hello", name)
}
deinit {
print("bye", name)
}
}
do {
let tracker = Tracker("t")
print("using", tracker.name)
}
print("after")
The do block makes a scope. When it ends, the last reference to the
tracker disappears, so “bye t” is printed before “after”. Two objects that
refer to each other can keep each other alive forever; the Swift book’s
Automatic Reference Counting chapter explains how weak references break
such cycles.
Lab 14: Objects
Q1: Vending machine
This is a CS61A lab classic. Write a VendingMachine class that sells one
product at one price. It starts with no stock and no funds.
restock(_:)adds stock and returns"Current <product> stock: <n>".addFunds(_:)returns"Current balance: $<n>", but if the machine is empty it keeps nothing and returns"Nothing left to vend. Please restock. Here is your $<n>.".vend()returns"Nothing left to vend. Please restock."when empty,"Please add $<n> more funds."when short, and otherwise sells one item, returning"Here is your <product>."or"Here is your <product> and $<n> change.". Change is returned, so the balance goes back to 0.
Show solution
class VendingMachine {
let product: String
let price: Int
private var stock = 0
private var funds = 0
init(product: String, price: Int) {
self.product = product
self.price = price
}
func restock(_ amount: Int) -> String {
stock += amount
return "Current \(product) stock: \(stock)"
}
func addFunds(_ amount: Int) -> String {
if stock == 0 {
return "Nothing left to vend. Please restock. Here is your $\(amount)."
}
funds += amount
return "Current balance: $\(funds)"
}
func vend() -> String {
if stock == 0 {
return "Nothing left to vend. Please restock."
}
if funds < price {
return "Please add $\(price - funds) more funds."
}
let change = funds - price
stock -= 1
funds = 0
return change > 0 ? "Here is your \(product) and $\(change) change." : "Here is your \(product)."
}
}
let machine = VendingMachine(product: "candy", price: 10)
assert(machine.vend() == "Nothing left to vend. Please restock.")
assert(machine.addFunds(15) == "Nothing left to vend. Please restock. Here is your $15.")
assert(machine.restock(2) == "Current candy stock: 2")
assert(machine.vend() == "Please add $10 more funds.")
assert(machine.addFunds(7) == "Current balance: $7")
assert(machine.addFunds(5) == "Current balance: $12")
assert(machine.vend() == "Here is your candy and $2 change.")
assert(machine.addFunds(10) == "Current balance: $10")
assert(machine.vend() == "Here is your candy.")
assert(machine.addFunds(15) == "Nothing left to vend. Please restock. Here is your $15.")private keeps stock and funds inside the class, so the only way to
change them is through the three methods, which keep them consistent.
Q2: Savings with interest
Write SavingsAccount, a subclass of Account (as defined in this lesson,
with deposit and withdraw), that adds a method addInterest() which
increases the balance by Account.interestRate, rounded down to a whole
number, and returns the new balance. Withdrawals from a savings account
should be refused entirely (return the balance unchanged) when they would
leave less than 10.
Show solution
class Account {
static let interestRate = 0.02
var balance = 0
func deposit(_ amount: Int) -> Int {
balance += amount
return balance
}
func withdraw(_ amount: Int) -> Int {
if amount > balance {
return balance
}
balance -= amount
return balance
}
}
class SavingsAccount: Account {
static let minimumBalance = 10
func addInterest() -> Int {
balance += Int(Double(balance) * Account.interestRate)
return balance
}
override func withdraw(_ amount: Int) -> Int {
if balance - amount < SavingsAccount.minimumBalance {
return balance
}
return super.withdraw(amount)
}
}
let savings = SavingsAccount()
assert(savings.deposit(500) == 500)
assert(savings.addInterest() == 510)
assert(savings.withdraw(505) == 510) // would leave 5, refused
assert(savings.withdraw(500) == 10)addInterest is new, so it needs no override; withdraw replaces an
inherited method, so it does. Int(...) of a Double rounds toward zero.
Q3: Shared or copied?
Predict what this program prints, then run it.
struct PointValue {
var x = 0
}
class PointObject {
var x = 0
}
var v1 = PointValue()
var v2 = v1
v2.x = 5
let o1 = PointObject()
let o2 = o1
o2.x = 5
print(v1.x, o1.x)
Show solution
It prints 0 5. The structure is copied, so changing v2 leaves v1 at 0.
The class instance is shared, so changing it through o2 is visible through
o1. The two type definitions are identical except for one keyword.
What’s next
Next lesson: protocols and generics, for code that works with many types at once.