Structs, part 1
How to create your own structs
A struct is a custom, complex data type complete with their own variables and functions.
struct Album { let title: String let artist: String let year: Int func printSummary() { print("\(title) (\(year)) by \(artist)") } }
A struct when named is capitalized. All data types are capitalized.
// a couple of albums let red = Album(title: "Red", artists: "TS", year: 2000) let wings = Album(title: "Wings", artists: "BTX", year: 2020) print(red.title) print(wings.artist) red.printSummary() wings.printSummary()
We can have values that change based on parameters.
struct Employee { let name: String var vacationRemaining: Int func takeVacation(days: Int) { if vacationRemaining > days { vacationRemaining -= days print("I'm going on vacation!") print("Days remaining: \(vacationRemaining)") } else { print("Oops! There aren't enough days remaining.") } } }
But if we create an Employee with a let, Swift will change everything in Employee into a constant. So we wan to add the keyword mutating
.
Like this:
mutating func takeVacation(days: Int) {}
From here on out, we want to avoid using let
to define a instance of a struct. Swift won’t build it as let
and mutating
doesn’t go together.
A couple more deets:
- variables and constants that belong to structs are called properties
- Functions that belong to structs are called methods
- when we create a constant or a variable out of a struct, we call that an
instance
- The initializer for an instance of a struct, it kinda looks like a function call.
Album(title: "Wings", artist: "A", year: 2020)
The way swift handles structs, it silently adds an init function to the struct and passes its properties into that. So these are equivalent:
:
var archer1 = Employee(name: "Sterling Archer", vacationRemaining: 14) var archer2 = Employee.init(name: "Sterling Archer", vacationRemaining: 14)
Swift will even insert default values to structs. Given these 2 properties:
let name: String var vacationRemaining = 14 // gets turned into a default
So both of these are valid:
let kane = Employee(name: "Lana Kane") let poovey = Employee(name: "Pam Poovey", vacationRemaining: 35)
Scored 11/12 on Creating your own structs
Scored 10/12 on mutating methods
How to compute property values dynamically
Structs can have 2 kinds of properties
- stored property – a variable or constant that holds data inside an instance of the struct
- computed property – calculates the value of the property dynamically every time its accessed. They are a blend of stored properties and function, accessed like properties but work like functions
We had an example of an employee struct
struct Employee { let name: String var vacationRemaining: Int } var archer = Employee(name: "Sterling Archer", vacationRemaining: 14) archer.vacationRemaining -= 5 print(archer.vacationRemaining) archer.vacationRemaining -= 3 print(archer.vacationRemaining)
With this struct, we lose the information on how many vacation days they were originally granted.
We could adjust to:
struct Employee { let name: String var vacationAllocated = 14 var vacationTaken = 0 var vacationRemaining: Int { vacationAllocated = vacationTaken } }
Here vacationRemaining
is a value that is calculated dynamically but is accessed like a stored property.
var archer = Employee(name: "S A". vacationAllocated: 14) archer.vacationTaken += 4 print(archer.vacationRemaining) archer.vacationTaken += 4 print(archer.vacationRemaining)
in order to write to the stored property more modification is necessary. We need to use get
& set
in the computed property. These are fancy names for code that reads and code that sets.
.
“`swift
var vacationRemaining: Int {
// this stays the same
get {
vacationAllocated – vacationTaken
}
set { vacationAllocated = vacationTaken + newValue }
}
With the getter and setter in place, we can now modify `vacationRemaining`.
swift
var archer = Employee(name: “s a”, vacationAllocated: 14)
archer.vacationTaken += 4
archer.vacationRemaining = 5
print(archer.vacationAllocated)
“`
When to use:
- If you read the property regularly, when the value hasn’t changed – then use a store property. It will be much faster than using a calculated property
- If a property is read very rarely, or not at all, then using a computed property saves you from having to calculate its value and store it somewhere
Scored 6/12 on Computed properties.
Note
Kinda annoyed at this test, I feel like the gotchas were a bit excessive. Questioning on past things we learned vs what this module was about. I’ll be a bit more careful next time.
Also
I’m not running the questions in a Swift playground before answering the questions, I’m looking at the code and trying to ascertain what the issues might be or what the answers are.
How to take action when a property changes
A property observer
is a piece of code that run when properties change.
They take 2 forms:
- didSet – runs when the property just changed
- willSet – runs before the property changed
changed
struct Game { var score = 0 } var game = Game() game.score += 10 print("Score is now \(game.score)") game.score -= 3 print("Score is now \(game.score)") game.score += 1
That creates Game
struct, and modifies score
. Each time the score changes, we print the value to keep track of it. Except for the last iteration, the bug is the score doesn’t get printed out.
Note: the Swift code is valid. But the missing print statement is a mistake.
Solving with property observers:
struct Game { var score = 0 { print("Score is now \(score)") } } var game = Game() game.score += 10 game.score -= 5 game.score -= 3
Note
Swift provides the constantoldValue
insidedidSet
, in case you need to reference the original value.
willSet
provides the constantnewValue
to reference the value that will be derived from the computed property.
.
“`swift
struct App {
var contacts = [String]() {
willSet {
print(newValue)
}
didSet {
print(oldValue)
}
}
}
var app = App()
app.contacts.append(“Name”)
> Note > > Do not use these observers to do any slow work. > > Most of the time we will use `didSet` more than `willSet` because we want to do something after the property changes & not before. > > Most times `willSet` is used to check app state before some change occurs in the app Scored 10/12 on property observers. ## How to create custom initializers Swift silently creates a method called init for every struct that gets instantiated. We can also create our own. Syntax:
swift
init(params)
full example of a member-wise initializer:
swift
struct Player {
let name: String
let number: Int
}
let player = Player(name: “Megan R”, number: 15)
Now with custom init func
swift
struct Player {
let name: String
let number: Int
init(name: String, number: Int) { self.name = name self.number = number }
}
“`
Note
- init is a function that doesn’t use the func keyword
- init doesn’t return a type, they return the struct they belong to
self
basically means assign the parameter to the property
With a custom init fun we can initialize the struct properties however we’d like
struct Player { let name: String let number: Int init(name: String) { self.name = name number = Int.random(in: 1...99) } } let player = Player(name: "Megan R") print(player.number)
Remember
All properties in the initializer need to have a value by the time the initializer ends. Swift will not build it otherwise
Your custom initializer takes the place of the default one Swift creates.
If you want to maintain the default member-wise initializer, take the custom init out of the struct and add it to an extension, like so:
struct Employee { var name: String var yearsActive = 0 } extension Employee { init() { self.name = "Anonymous" print("Creating an anonymous employee…") } } // creating a named employee now works let roslin = Employee(name: "Laura Roslin") // as does creating an anonymous employee let anon = Employee()
Scored 10/12 on Initializers
Scored 9/12 on referring to the current instance