Day 11
Day 11 êŽë š
Structs, part two
As youâve seen, structs let us combine individual pieces of data to make something new, then attach methods so we can manipulate that data.
Today youâre going to learn about some of the more advanced features of structs that make them more powerful, including static properties and access control â the art of stopping other parts of your code from meddling in places they ought not to be.
Thereâs a famous quote that is sadly anonymous, but I think it fits well here: âprivacy is power â what people donât know, they canât ruin.â As youâll see, the same is true in Swift: hiding access to certain properties and methods can actually make our code better, because there are fewer places able to access it.
As a reminder, both of these things are used extensively in SwiftUI, so itâs worth taking the time to master them now because theyâll be in use from our very first project onwards.
Today you have two tutorials to work through, where youâll meet multiple levels of access control, and also the ability to create static properties and methods. Once youâve watched each video and any optional extra reading you wanted, thereâs a short test to help make sure youâve understood what was taught.
1. How to limit access to internal data using access control
1. How to limit access to internal data using access control
By default, Swiftâs structs let us access their properties and methods freely, but often that isnât what you want â sometimes you want to hide some data from external access. For example, maybe you have some logic you need to apply before touching your properties, or maybe you know that some methods need to be called in a certain way or order, and so shouldnât be touched externally.
We can demonstrate the problem with an example struct:
struct BankAccount {
var funds = 0
mutating func deposit(amount: Int) {
funds += amount
}
mutating func withdraw(amount: Int) -> Bool {
if funds >= amount {
funds -= amount
return true
} else {
return false
}
}
}
That has methods to deposit and withdraw money from a bank account, and should be used like this:
var account = BankAccount()
account.deposit(amount: 100)
let success = account.withdraw(amount: 200)
if success {
print("Withdrew money successfully")
} else {
print("Failed to get the money")
}
But the funds
property is just exposed to us externally, so whatâs stopping us from touching it directly? The answer is nothing at all â this kind of code is allowed:
account.funds -= 1000
That completely bypasses the logic we put in place to stop people taking out more money than they have, and now our program could behave in weird ways.
To solve this, we can tell Swift that funds
should be accessible only inside the struct â by methods that belong to the struct, as well as any computed properties, property observers, and so on.
This takes only one extra word:
private var funds = 0
And now accessing funds
from outside the struct isnât possible, but it is possible inside both deposit()
and withdraw()
. If you try to read or write funds
from outside the struct Swift will refuse to build your code.
This is called _ _, because it controls how a structâs properties and methods can be accessed from outside the struct.
Swift provides us with several options, but when youâre learning youâll only need a handful:
- Use
private
for âdonât let anything outside the struct use this.â - Use
fileprivate
for âdonât let anything outside the current file use this.â - Use
public
for âlet anyone, anywhere use this.â
Thereâs one extra option that is sometimes useful for learners, which is this: private(set)
. This means âlet anyone read this property, but only let my methods write it.â If we had used that with BankAccount
, it would mean we could print account.funds
outside of the struct, but only deposit()
and withdraw()
could actually change the value.
In this case, I think private(set)
is the best choice for funds: you can read the current bank account balance at any time, but you canât change it without running through my logic.
If you think about it, access control is really about limiting what you and other developers on your team are able to do â and thatâs sensible! If we can make Swift itself stop us from making mistakes, thatâs always a smart move.
Important: If you use private
access control for one or more properties, chances are youâll need to create your own initializer.
1. How to limit access to internal data using access control - Additional
- Optional: Whatâs the point of access control?
- Test: Access control
2. Static properties and methods
2. Static properties and methods
Youâve seen how we can attach properties and methods to structs, and how each struct has its own unique copy of those properties so that calling a method on the struct wonât read the properties of a different struct from the same type.
Well, sometimes â only sometimes â you want to add a property or method to the struct itself, rather than to one particular instance of the struct, which allows you to use them directly. I use this technique a lot with SwiftUI for two things: creating example data, and storing fixed data that needs to be accessed in various places.
First, letâs look at a simplified example of how to create and use static properties and methods:
struct School {
static var studentCount = 0
static func add(student: String) {
print("\(student) joined the school.")
studentCount += 1
}
}
Notice the keyword static
in there, which means both the studentCount
property and add()
method belong to the School
struct itself, rather than to individual instances of the struct.
To use that code, weâd write the following:
School.add(student: "Taylor Swift")
print(School.studentCount)
I havenât created an instance of School
â we can literally use add()
and studentCount
directly on the struct. This is because those are both static, which means they donât exist uniquely on instances of the struct.
This should also explain why weâre able to modify the studentCount
property without marking the method as mutating
â thatâs only needed with regular struct functions for times when an instance of struct was created as a constant, and there is no instance when calling add()
.
If you want to mix and match static and non-static properties and methods, there are two rules:
- To access non-static code from static code⊠youâre out of luck: static properties and methods canât refer to non-static properties and methods because it just doesnât make sense â which instance of
School
would you be referring to? - To access static code from non-static code, always use your typeâs name such as
School.studentCount
. You can also useSelf
to refer to the current type.
Now we have self
and Self
, and they mean different things: self
refers to the current value of the struct, and Self
refers to the current type.
Tip: Itâs easy to forget the difference between self
and Self
, but if you think about it itâs just like the rest of Swiftâs naming â we start all our data types with a capital letter (Int
, Double
, Bool
, etc), so it makes sense for Self
to start with a capital letter too.
Now, that sound you can hear is a thousand other learners saying âwhy the heck is this needed?â And I get it â this can seem like a rather redundant feature at first. So, I want to show you the two main ways I use static data.
First, I use static properties to organize common data in my apps. For example, I might have a struct like AppData
to store lots of shared values I use in many places:
struct AppData {
static let version = "1.3 beta 2"
static let saveFilename = "settings.json"
static let homeURL = "https://www.hackingwithswift.com"
}
Using this approach, everywhere I need to check or display something like my appâs version number â an about screen, debug output, logging information, support emails, etc â I can read AppData.version
.
The second reason I commonly use static data is to create examples of my structs. As youâll see later on, SwiftUI works best when it can show previews of your app as you develop, and those previews often require sample data. For example, if youâre showing a screen that displays data on one employee, youâll want to be able to show an example employee in the preview screen so you can check it all looks correct as you work.
This is best done using a static example
property on the struct, like this:
struct Employee {
let username: String
let password: String
static let example = Employee(username: "cfederighi", password: "hairforceone")
}
And now whenever you need an Employee
instance to work with in your design previews, you can use Employee.example
and youâre done.
Like I said at the beginning, there are only a handful of occasions when a static property or method makes sense, but they are still a useful tool to have around.
2. Static properties and methods - Additional
3. Summary: Structs
3. Summary: Structs
Structs are used almost everywhere in Swift: String
, Int
, Double
, Array
and even Bool
are all implemented as structs, and now you can recognize that a function such as isMultiple(of:)
is really a method belonging to Int
.
Letâs recap what else we learned:
- You can create your own structs by writing
struct
, giving it a name, then placing the structâs code inside braces. - Structs can have variable and constants (known as properties) and functions (known as methods)
- If a method tries to modify properties of its struct, you must mark it as
mutating
. - You can store properties in memory, or create computed properties that calculate a value every time they are accessed.
- We can attach
didSet
andwillSet
property observers to properties inside a struct, which is helpful when we need to be sure that some code is always executed when the property changes. - Initializers are a bit like specialized functions, and Swift generates one for all structs using their property names.
- You can create your own custom initializers if you want, but you must always make sure all properties in your struct have a value by the time the initializer finishes, and before you call any other methods.
- We can use access to mark any properties and methods as being available or unavailable externally, as needed.
- Itâs possible to attach a property or methods directly to a struct, so you can use them without creating an instance of the struct.
4. Checkpoint 6
4. Checkpoint 6
Structs sit at the core of every SwiftUI app, so itâs really important you take some extra time to make sure you understand what they do and how they work.
To check your knowledge, hereâs a small task for you: create a struct to store information about a car, including its model, number of seats, and current gear, then add a method to change gears up or down. Have a think about variables and access control: what data should be a variable rather than a constant, and what data should be exposed publicly? Should the gear-changing method validate its input somehow?
As always Iâll write some hints below, but first Iâm going to leave some space so you donât see the hints by accident. As always, itâs a really good idea to try this challenge yourself before looking at the hints â itâs the fastest way to identify parts you feel less confident with.
Still here? Okay, here are some hints:
- A carâs model and seat count arenât going to change once produced, so they can be constant. But its current gear clearly does change, so make that a variable.
- Changing gears up or down should ensure such a change is possible â there is no gear 0, for example, and itâs safe to assume a maximum of 10 gears should cover most if not all cars.
- If you use
private
access control, you will probably also need to create your own initializer. (Isprivate
the best choice here? Try it for yourself and see what you think!) - Remember to use the
mutating
keyword on methods that change properties!
Thatâs day 11 complete, so hopefully by now you know what to do: go and post about your progress online on Twitter, Facebook, Reddit, or whatever social media you like.