Day 9 - 100 Days of Swift

6 minute read

Structs (part 2)

Day 9 is the second day about Structs. You look at initializing instances of your structs and how to create your own init function. You look at how to refer to the current instance of a struct. You look at lazy properties that get created when they are first accessed and static properties and methods that are shared across all instances of the type. And finally, you look at controlling access to information from outside of the struct with private.

Most of the time, the compiler can generate a memberwise initializer for your custom structs without you having to write it. This means that you get an initializer which takes in parameters matching the names and types of the structs properties for free. There are certain cases where this can’t be generated though, and sometimes you just want to write your own that does more or different work than the generated one. You can do that like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Office {
    var address: String
    var employees: [Employee]
}
// Gets a generated memberwise initializer

struct CoworkingSpace {
    var address: String
    var occupants: [Person]

    init(currentAddress: String,
         currentOccupants: [Person] = []) {
        address = currentAddress
        occupants = currentOccupants
    }
}
// This one replaces the generated initializer with
// a custom initializer that has an empty array for
// currentOccupants by default,
// but lets the user pass one in if they want.

If you need to refer to the current instance of your struct, you can do that with the self keyword:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// The previous custom initializer could be
// rewritten like this:
struct CoworkingSpace {
    var address: String
    var occupants: [Person]

    init(address: String, cccupants: [Person] = []) {
        self.address = address
        self.occupants = occupants
    }
}
// 'self' lets you distinguish between the
// instance property and the parameter passed in
// to the init method.

Sometimes it takes a while to initialize a property, and you may not even need it. That is where the lazy keyword comes in. It lets you tell the compiler that you don’t want to instantiate this property until the first time someone tries to access it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct UnbilledHours {
    var total = 0.0
    init() {
        // It can take a while to calculate this
    }
}

struct Office {
    var employees: [Employee]
    // Since we don't always need to know
    // the unbilledHours we can mark it as 'lazy'
    // and skip that work until we need it
    lazy var unbilledHours = UnbilledHours()

    init(employees: [Employee]){
        self.employees = employees
    }
}

If you need a property that is shared across all instances of a type, you can use the static keyword to say that it belongs to the type, not the instance. The same is true for methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Office {
    static var numberOfOfficesManaged: Int = 0

    static func closeOffice() {
        numberOfOfficesManaged -= 1
    }

    var employees: [Employee]

    init(employees: [Employee]){
        self.employees = employees
        Office.numberOfOfficesManaged += 1
    }
}

These are accessed on the type itself then, instead of an instance:

1
2
3
4
5
6
7
8
9
10
print(Office.numberOfOfficesManaged) // 0

let jim = Employee(name: "Jim")
let dwight = Employee(name: "Dwight")
let andy = Employee(name: "Andy")
let theOffice = Office(employees: [jim, dwight, andy])

print(Office.numberOfOfficesManaged) // 1
Office.closeOffice()
print(Office.numberOfOfficesManaged) // 0

If you don’t want anyone outside of a struct to have access to a property, you can mark it private. This can be helpful for maintaining good separation of concerns.

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
struct Office {
    var employees: [Employee]
    private var passcode: String =
    "the coffee in Peru is much hotter"

    init(employees: [Employee]){
        self.employees = employees
    }

    func attemptToEnterOffice(with passcode: String) {
        if passcode == self.passcode {
            print("Come on in!")
        } else {
            print("Sorry, that's wrong")
            print("Give em the steam!")
        }
    }
}


let jim = Employee(name: "Jim")
let dwight = Employee(name: "Dwight")
let andy = Employee(name: "Andy")
let theOffice = Office(employees: [jim, dwight, andy])

let guess = "the coffee in Peru is far hotter"
theOffice.attemptToEnterOffice(with: guess)
// "Sorry, that's wrong"
// "Give em the steam!"

Reflections

Nine days in. I am still learning stuff, but I am starting to feel a little worn down by this process. I missed another day. Seems like Sundays just aren’t working out great for me to do this and get a blog post out. I think I may modify the schedule a little bit and do 100 Days of Swift six days a week and just not expect myself to get anything done on Sundays. That’ll still be a good pace, and it seems to be a more accurate reflection of what I am actually capable of achieving amidst all the other things going on in my life. If my quick math is right, that should mean I’ll finish the hundred days of Swift in about a hundred and fifteen days. That’s a timeline I can live with.

Today I learned that the compiler can’t generate a memberwise initializer for a struct that has private properties. I’m not exactly sure what that means, or if it is the best way to put it, because I have seen a couple of cases where it looks like the compiler is doing exactly that. But at least it is safe to say that private properties may not play very nicely with the code that generates initializers. That’s okay though because, in my opinion, it is usually better to write out your initializers anyways. It makes your intention explicit, and it takes a negligible amount of time.