Day 15 - 100 Days of Swift

7 minute read

Third Review Day

Day 15 is the third review day. You review properties, property observers, and computed properties. You review static properties and methods. You review access control, polymorphism, and typecasting. And you briefly review closures.

Properties are the variables and constants that are contained in an instance of a type. You can put observers on them with the willSet and didSet keywords. These blocks of code will run either before or after the property is set. Computed properties don’t store an actual value, but they run a block of code each time they are accessed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class CornBaller {
    let serialNumber: Int
    var isCooking: Bool = false {
        willSet {
            if newValue {
                print("Gonna fry up some corn balls")
            } else {
                print("I'm turning this thing off!")
            }
        }
        didSet {
            if isCooking {
                print("Ow! Damn that's hot.")
            } else {
                print("There! Now you can't hurt anyone anymore")
            }
        }
    }

    var isHot: Bool {
        return isCooking
    }

    init(serialNumber: Int) {
        self.serialNumber = serialNumber
    }
}

let firstCornballer = CornBaller(serialNumber: 1)
firstCornballer.isCooking.toggle()
// "Gonna fry up some corn balls"
// "Ow! Damn that's hot."

print(firstCornballer.isHot) // true

firstCornballer.isCooking.toggle()
// "I'm turning this thing off!"
// "There! Now you can't hurt anyone anymore"

print(firstCornballer.isHot) // false

You can give properties and methods to the type, instead of to each instance of the type, with the static keyword. These are accessed on the type itself, not an instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CornBaller {
    // Everything else is the same...

    static var numberMade: Int = 0

    static func newSerialNumber() -> Int {
        numberMade += 1
        return numberMade
    }

    init() {
        self.serialNumber = CornBaller.newSerialNumber()
    }
}

let firstCornballer = CornBaller()
let secondCornballer = CornBaller()
print(firstCornballer.serialNumber)  // 1
print(secondCornballer.serialNumber) // 2

You can control who has access to properties or methods with various keywords. public, internal, fileprivate and private all restrict your code to different degrees:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Public means it can be accessed by other modules that import
// this module. Least restrictive.
public let openSecret = "Dipping into the kitty"

// Internal means it can only be accessed within its
// defining module. This is the default.
internal let companySecret = "Not making money"

// File private means it can be accessed within the source file
// it is defined in.
fileprivate let familySecret = "Light treason"

// Private means it can only be used by the entity enclosing
// its declaration. Most restrictive.
private let georgeMichaelsCrush = "Maebe"

Classes that inherit from other classes can be treated both as their parent class and their own class at the same time. This is called polymorphism. If you know that an object is another class than the compiler knows, you can cast it with as, as? or as!:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Person {
    var firstName: String
    var lastName: String
    var profession: String

    init(firstName: String, lastName: String, profession: String) {
        self.firstName = firstName
        self.lastName = lastName
        self.profession = profession
    }

    func introduce() {
        var string = "Hi, my name is \(firstName) \(lastName)"
        string += " and I am a \(profession)"
        print(string)
    }
}

class Doctor: Person {
    init(firstName: String, lastName: String) {
        super.init(firstName: firstName,
                   lastName: lastName,
                   profession: "Doctor")
    }

    override func introduce() {
        var string = "I am a doctor!"
        string += " And my name is \(firstName) \(lastName)"
        print(string)
    }
}

class Actor: Person {
    init(firstName: String, lastName: String) {
        super.init(firstName: firstName,
                   lastName: lastName,
                   profession: "Actor")
    }

    override func introduce() {
        var string = "My name is \(firstName) \(lastName)"
        string += " and I love to act!"
        print(string)
    }
}

let gob = Person(firstName: "Gob",
                 lastName: "Bluth",
                 profession: "Magician")
let tobias = Doctor(firstName: "Tobias",
                    lastName: "Fünke")
let carl = Actor(firstName: "Carl",
                 lastName: "Weathers")

let people: [Person] = [gob, tobias, carl]

for person in people {
    person.introduce()

    if let person = person as? Actor {
        print("I'm off to an audition!")
    }
}
// This is an example of polymorphism. Each of these is acting
// as a Person, and some of them are also acting
// as Doctors/Actors.
// "Hi, my name is Gob Bluth and I am a Magician"
// "I am a doctor! And my name is Tobias Fünke"
// "My name is Carl Weathers and I love to act!"

// This will only print once, for Carl,
// because he is the only Person who can be cast as an Actor.
// "I'm off to an audition!"

Closures are blocks of code that are stored in a variable to be run later. They capture the variables they use, which is usually a good and convenient thing, but can lead to retention cycles in certain cases. Try to avoid those.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Actor: Person {
    var makeStew: ([String]) -> Void = { _ in }

    // Everything else is the same...
}

let ingredients = ["beef", "celery", "carrots"]

// By default, Carl doesn't know how to make stew
carl.makeStew(ingredients) // Nothing happens...

// We can give him some code to run
carl.makeStew = { (ingredients: [String]) in
    for ingredient in ingredients {
        print("Add the \(ingredient)...")
    }
    print("And baby, you got a stew going.")
}

// Now he knows what to do
carl.makeStew(ingredients)
// "Add the beef..."
// "Add the celery..."
// "Add the carrots..."
// "And baby, you got a stew going."

Reflections

Today I learned about polymorphism. When he was describing the situation with the different subclasses, my guess was that since they were all in an array of the parent class, the parent class’s method would get called on each of them. But apparently that is not the case. I had to run the code on my own to believe it, but it is true. Even though the compiler is told they are an array of the parent class, because they are actually instances of the child classes, and the child classes override the parent class’s method, the child class implementations get called even without typecasting. So they are acting as instances of both classes at the same time: polymorphism.