struct Post: BlogDelegate
Table of Contents
A couple days into school, we covered delegates and protocols. I definitely saw some fellow students struggle with the concept. I wish I could say I have a perfect metaphor to help with the understanding, but my comparison ultimately relies on having some existing understanding of common software development patterns.
Classes
I’d first like to briefly illustrate how classes work to give us something to compare against.
In a typical superclass/subclass model, the superclass is defined with various properties and methods that a subclass then inherits in their entirety.
Subclassing Example
In this example, we will create some basic classes representing some things on a farm.
// superclass
class FarmObject {
let weight: Int
init(weight: Int) {
self.weight = weight
}
}
// subclasses
class FarmVehicle: FarmObject {
let wheelCount: Int
let soundItMakes: String
let horsePower: Int
init(weight: Int, wheelCount: Int, soundItMakes: String, horsePower: Int) {
self.wheelCount = wheelCount
self.soundItMakes = soundItMakes
self.horsePower = horsePower
super.init(weight: weight)
}
func makeNoise() {
print(soundItMakes)
}
}
class FarmAnimal: FarmObject {
let legCount: Int
let soundItMakes: String
let isAHorse: Bool // for the luls - would actually be smarter to make a horse subclass :P
let name: String
init(weight: Int, name: String, legCount: Int, soundItMakes: String, isAHorse: Bool) {
self.legCount = legCount
self.soundItMakes = soundItMakes
self.isAHorse = isAHorse
self.name = name
super.init(weight: weight)
}
func makeNoise() {
print("\(name) goes \"\(soundItMakes)!\"")
}
}
class FarmBuilding: FarmObject {
let numberOfWalls: Int
let name: String
init(weight: Int, name: String, numberOfWalls: Int) {
self.numberOfWalls = numberOfWalls
self.name = name
super.init(weight: weight)
}
}
class Horse: FarmAnimal {
init(weight: Int) {
super.init(weight: weight, name: "Sparkles", legCount: 4, soundItMakes: "Naaay", isAHorse: true)
}
}
let myPony = Horse(weight: 900)
myPony.makeNoise()
In this example, all three subclasses inherited the weight property definition as well as everything else they defined independently. However, I want you to notice how both FarmAnimal
and FarmVehicle
have to each define soundItMakes
and makeNoise()
, effectively duplicating our code. It wouldn’t make sense to have those on a FarmBuilding
because buildings don’t make sounds. However, it also doesn’t work to put the property on the superclass FarmObject
because, even though FarmAnimal
and FarmVehicle
would stop duplicating their code, FarmBuilding
we would still result in having this strange functionality.
In the same vein, it makes sense for both a FarmAnimal
and FarmBuilding
to have names (you might refer to different buildings as "house" or "barn", but yeah it’s a bit of a stretch) but it’s not as common to have names for a FarmVehicle
.
The primary takeaway I want you to understand is that the inheritance in inherently linear with subclassing.
Protocols
Next I’d like to explain protocols. I’ll skip the formal definition and go right to the comparison. I like to think of protocols as the next evolution of subclassing.
Protocol Example
Using the previous example as a starting place, I’ll reorganize the setup to use protocols instead.
protocol FarmObject {
var weight: Int { get }
}
protocol FarmNoiseMaker {
var soundItMakes: String { get }
func makeNoise()
}
extension FarmNoiseMaker {
func makeNoise() {
print(soundItMakes)
}
}
protocol Identifiable {
var name: String { get }
}
protocol FarmVehicle: FarmObject, FarmNoiseMaker {
var wheelCount: Int { get }
var horsePower: Int { get }
}
protocol FarmAnimal: FarmObject, FarmNoiseMaker, Identifiable {
var legCount: Int { get }
var isAHorse: Bool { get }
}
protocol FarmBuilding: FarmObject, Identifiable {
var wallCount: Int { get }
}
At first glace, this looks greatly simplified, but also strange, if you’re not familiar with protocols. If you’re just getting started, feel free to skip over the advanced section (or come back when you’re more comfortable with the concept!).
But with those changes, now we can do
struct Horse: FarmAnimal {
let legCount: Int
let isAHorse: Bool
let weight: Int
let soundItMakes: String
}
let myPony = Horse(name: "Sparkles", legCount: 4, isAHorse: true, weight: 900, soundItMakes: "Naaaaaay!")
myPony.makeNoise() // prints "Naaaaaay!"
The biggest thing to take away here is that protocols act more like tags in metadata. Essentially, you can pick and choose protocols to conform to (aka inherit from) and skip over the ones that are irrelevant to the object type. Any class
or struct
you tag with a protocol now inherits everything the protocol has to offer, but simultaneously doesn’t require doing so linearly! See how for both FarmVehicle
and FarmAnimal
we conform to not only FarmObject
, but also FarmNoiseMaker
? Additionally, as long as you don’t tell your protocol to only work with class types, it can even work with structs. But at the very least, you should see that we can save ourselves a lot of boilerplate code!
If it’s not obvious, this is extremely powerful.
Procotols Advanced
The additional concepts here are
-
Protocol Extensions
-
Nothing is optional in protocol implementation. If a protocol declares that it needs a function or property, anything conforming to it is REQUIRED to implement those details. However, Protocol Extensions can provide default implementations for functions, even using the very properties the protocol defines! The example above uses the protocol extension for makeNoise()
, but we can easily define our own functionality by writing the struct like this:
struct Horse: FarmAnimal {
let name: String
let legCount: Int
let isAHorse: Bool
let weight: Int
let soundItMakes: String
func makeNoise() {
print("\(name) says: \(soundItMakes)")
}
}
let myPony = Horse(name: "Sparkles", legCount: 4, isAHorse: true, weight: 900, soundItMakes: "Naaaaaay!")
myPony.makeNoise() // now prints "Sparkles says: Naaaaaay!"
- If the extension didn’t exist, we’d be required to write the
makeNoise()
function
- Protocol Syntax
- note the following things:
- the lack of
override
in the function above
- that everything defined in a protocol is a
var
and NOT a let
- BUT you can conform to the protocol by declaring a
let
in place of the var, as long as the var
is { get }
only! (as opposed to { get set }
)
- Protocol Composition
- you may combine one or more protocols to create a new one! This was how
FarmVehicle
and FarmAnimal
were created!
Delegates
Finally, delegates. Honestly, if you understand protocols, you already understand delegates. Delegates are just a certain type of protocol, just used in a consistent pattern, typically in a callback or data getting kind of idea.
A couple days into school, we covered delegates and protocols. I definitely saw some fellow students struggle with the concept. I wish I could say I have a perfect metaphor to help with the understanding, but my comparison ultimately relies on having some existing understanding of common software development patterns.
Classes
I’d first like to briefly illustrate how classes work to give us something to compare against.
In a typical superclass/subclass model, the superclass is defined with various properties and methods that a subclass then inherits in their entirety.
Subclassing Example
In this example, we will create some basic classes representing some things on a farm.
// superclass
class FarmObject {
let weight: Int
init(weight: Int) {
self.weight = weight
}
}
// subclasses
class FarmVehicle: FarmObject {
let wheelCount: Int
let soundItMakes: String
let horsePower: Int
init(weight: Int, wheelCount: Int, soundItMakes: String, horsePower: Int) {
self.wheelCount = wheelCount
self.soundItMakes = soundItMakes
self.horsePower = horsePower
super.init(weight: weight)
}
func makeNoise() {
print(soundItMakes)
}
}
class FarmAnimal: FarmObject {
let legCount: Int
let soundItMakes: String
let isAHorse: Bool // for the luls - would actually be smarter to make a horse subclass :P
let name: String
init(weight: Int, name: String, legCount: Int, soundItMakes: String, isAHorse: Bool) {
self.legCount = legCount
self.soundItMakes = soundItMakes
self.isAHorse = isAHorse
self.name = name
super.init(weight: weight)
}
func makeNoise() {
print("\(name) goes \"\(soundItMakes)!\"")
}
}
class FarmBuilding: FarmObject {
let numberOfWalls: Int
let name: String
init(weight: Int, name: String, numberOfWalls: Int) {
self.numberOfWalls = numberOfWalls
self.name = name
super.init(weight: weight)
}
}
class Horse: FarmAnimal {
init(weight: Int) {
super.init(weight: weight, name: "Sparkles", legCount: 4, soundItMakes: "Naaay", isAHorse: true)
}
}
let myPony = Horse(weight: 900)
myPony.makeNoise()
In this example, all three subclasses inherited the weight property definition as well as everything else they defined independently. However, I want you to notice how both FarmAnimal
and FarmVehicle
have to each define soundItMakes
and makeNoise()
, effectively duplicating our code. It wouldn’t make sense to have those on a FarmBuilding
because buildings don’t make sounds. However, it also doesn’t work to put the property on the superclass FarmObject
because, even though FarmAnimal
and FarmVehicle
would stop duplicating their code, FarmBuilding
we would still result in having this strange functionality.
In the same vein, it makes sense for both a FarmAnimal
and FarmBuilding
to have names (you might refer to different buildings as "house" or "barn", but yeah it’s a bit of a stretch) but it’s not as common to have names for a FarmVehicle
.
The primary takeaway I want you to understand is that the inheritance in inherently linear with subclassing.
Protocols
Next I’d like to explain protocols. I’ll skip the formal definition and go right to the comparison. I like to think of protocols as the next evolution of subclassing.
Protocol Example
Using the previous example as a starting place, I’ll reorganize the setup to use protocols instead.
protocol FarmObject {
var weight: Int { get }
}
protocol FarmNoiseMaker {
var soundItMakes: String { get }
func makeNoise()
}
extension FarmNoiseMaker {
func makeNoise() {
print(soundItMakes)
}
}
protocol Identifiable {
var name: String { get }
}
protocol FarmVehicle: FarmObject, FarmNoiseMaker {
var wheelCount: Int { get }
var horsePower: Int { get }
}
protocol FarmAnimal: FarmObject, FarmNoiseMaker, Identifiable {
var legCount: Int { get }
var isAHorse: Bool { get }
}
protocol FarmBuilding: FarmObject, Identifiable {
var wallCount: Int { get }
}
At first glace, this looks greatly simplified, but also strange, if you’re not familiar with protocols. If you’re just getting started, feel free to skip over the advanced section (or come back when you’re more comfortable with the concept!).
But with those changes, now we can do
struct Horse: FarmAnimal {
let legCount: Int
let isAHorse: Bool
let weight: Int
let soundItMakes: String
}
let myPony = Horse(name: "Sparkles", legCount: 4, isAHorse: true, weight: 900, soundItMakes: "Naaaaaay!")
myPony.makeNoise() // prints "Naaaaaay!"
The biggest thing to take away here is that protocols act more like tags in metadata. Essentially, you can pick and choose protocols to conform to (aka inherit from) and skip over the ones that are irrelevant to the object type. Any class
or struct
you tag with a protocol now inherits everything the protocol has to offer, but simultaneously doesn’t require doing so linearly! See how for both FarmVehicle
and FarmAnimal
we conform to not only FarmObject
, but also FarmNoiseMaker
? Additionally, as long as you don’t tell your protocol to only work with class types, it can even work with structs. But at the very least, you should see that we can save ourselves a lot of boilerplate code!
If it’s not obvious, this is extremely powerful.
Procotols Advanced
The additional concepts here are
-
Protocol Extensions
-
Nothing is optional in protocol implementation. If a protocol declares that it needs a function or property, anything conforming to it is REQUIRED to implement those details. However, Protocol Extensions can provide default implementations for functions, even using the very properties the protocol defines! The example above uses the protocol extension for
makeNoise()
, but we can easily define our own functionality by writing the struct like this:struct Horse: FarmAnimal { let name: String let legCount: Int let isAHorse: Bool let weight: Int let soundItMakes: String func makeNoise() { print("\(name) says: \(soundItMakes)") } } let myPony = Horse(name: "Sparkles", legCount: 4, isAHorse: true, weight: 900, soundItMakes: "Naaaaaay!") myPony.makeNoise() // now prints "Sparkles says: Naaaaaay!"
- If the extension didn’t exist, we’d be required to write the
makeNoise()
function
-
- Protocol Syntax
- note the following things:
- the lack of
override
in the function above - that everything defined in a protocol is a
var
and NOT alet
- BUT you can conform to the protocol by declaring a
let
in place of the var, as long as thevar
is{ get }
only! (as opposed to{ get set }
)
- BUT you can conform to the protocol by declaring a
- the lack of
- note the following things:
- Protocol Composition
- you may combine one or more protocols to create a new one! This was how
FarmVehicle
andFarmAnimal
were created!
- you may combine one or more protocols to create a new one! This was how
Delegates
Finally, delegates. Honestly, if you understand protocols, you already understand delegates. Delegates are just a certain type of protocol, just used in a consistent pattern, typically in a callback or data getting kind of idea.