Agenda
- Limit access to internal data using access control.
- Static properties and methods.
- Summary: Structs
- Checkpoint 6
Limit access to internal data using access control
The properties of a struct can be accessed externally which creates loopholes in various applications. Let us consider the below example.
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
}
}
}
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")
}
In a normal scenario where this properties mean nothing your code does no harm. But, if you are using this say in a banking environment, you could easily access the property funds and get the money directly without the use of methods deposit or withdraw.
Like this account.funds -=1000
. The property funds can be controlled if you know the name of the instance. To solve this problem, we have to make sure that such properties can only be accessible by the methods inside the struct and cannot be accessed directly. This is where private
comes into picture private var funds = 0
. This is called as access control.
Let us look at some access control techniques:
- 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.” private(set)
used in cases where anyone can read the property but only the methods inside the struct can write to it.
If you are declaring multiple properties as private, you may need to create your own initializer.
Static properties and methods
We came across properties and methods that can be used by instances of a struct. But, you can also add properties and methods directly on a struct itself. We do this by using a keyword static.
struct School {
static var studentCount = 0
static func add(student: String) {
print("\(student) joined the school.")
studentCount += 1
}
}
School.add(student: "Taylor Swift")
print(School.studentCount)
This means the property studentCount and method add() don’t exist uniquely on instances of the struct. Also note that even though we writing to the property studentCount using add(), we don’t need to add the keyword mutating.
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.
struct Employee {
let username: String
let password: String
static let example = Employee(username: "cfederighi", password: "hairforceone")
}
Summary: Structs
- 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.
Checkpoint 6
Challenge
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?
Solution #1
I have chosen the properties to be public. I cannot think of a case where it needs to be public.
struct Car {
let model : String
let numOfSeats : Int
var currentGear : Int
mutating func gearUp() {
if currentGear < 6 {
currentGear += 1
}
else {
print("Maximum gear reached")
}
}
mutating func gearDown() {
if currentGear < 2 {
print("Minimum gear reached")
}
else{
currentGear -= 1
}
}
}var mazda = Car(model: "Mazda 3", numOfSeats: 4, currentGear: 2)
print(mazda.currentGear) // Prints 2
mazda.gearUp()
print(mazda.currentGear) //Prints 3
mazda.gearUp()
print(mazda.currentGear) // Prints 4mazda.gearDown()
print(mazda.currentGear)//Prints 3
Solution #2
For this solution, I have used private
for the property currentGear
and also created a custom initializer. Additionally, I have also added didSet{} for currentGear property so that we can print out the updated value every time it’s changed.
struct Car {let model : Stringlet numOfSeats : Intprivate var currentGear : Int {didSet {print("The gear is changed to \(currentGear)")}}mutating func gearUp() {if currentGear < 6 {currentGear += 1}else {print("Maximum gear reached")}}mutating func gearDown() {if currentGear < 2 {print("Minimum gear reached")}else{currentGear -= 1}}init(model: String, numOfSeats: Int) {self.model = modelself.numOfSeats = numOfSeatscurrentGear = 1}}var mazda = Car(model: "Mazda 3", numOfSeats: 4)mazda.gearUp()mazda.gearUp()mazda.gearDown()
It is printed as follows:
The gear is changed to 2The gear is changed to 3The gear is changed to 2
That is everything for today. See you tomorrow.