Updated for Xcode 14.2
Swift’s structs let us create our own custom, complex data types, complete with their own variables and their own functions.
A simple struct looks like this:
struct Album {
let title: String
let artist: String
let year: Int
func printSummary() {
print("\(title) (\(year)) by \(artist)")
}
}
That creates a new type called Album
, with two string constants called title
and artist
, plus an integer constant called year
. I also added a simple function that summarizes the values of our three constants.
Notice how Album
starts with a capital A? That’s the standard in Swift, and we’ve been using it all along – think of String
, Int
, Bool
, Set
, and so on. When you’re referring to a data type, we use camel case where the first letter is uppercased, but when you’re referring to something inside the type, such as a variable or function, we use camel case where the first letter is lowercased. Remember, for the most part this is only a convention rather than a rule, but it’s a helpful one to stick with.
At this point, Album
is just like String
or Int
– we can make them, assign values, copy them, and so on. For example, we could make a couple of albums, then print some of their values and call their functions:
let red = Album(title: "Red", artist: "Taylor Swift", year: 2012)
let wings = Album(title: "Wings", artist: "BTS", year: 2016)
print(red.title)
print(wings.artist)
red.printSummary()
wings.printSummary()
Notice how we can create a new Album
as if we were calling a function – we just need to provide values for each of the constants in the order they were defined.
As you can see, both red
and wings
come from the same Album
struct, but once we create them they are separate just like creating two strings.
You can see this in action when we call printSummary()
on each struct, because that function refers to title
, artist
, and year
. In both instances the correct values are printed out for each struct: red
prints “Red (2012) by Taylor Swift” and wings
prints out “Wings (2016) by BTS” – Swift understands that when printSummary()
is called on red
, it should use the title
, artist
, and year
constants that also belong to red
.
Where things get more interesting is when you want to have values that can change. For example, we could create an Employee
struct that can take vacation as needed:
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.")
}
}
}
However, that won’t actually work – Swift will refuse to build the code.
You see, if we create an employee as a constant using let
, Swift makes the employee and all its data constant – we can call functions just fine, but those functions shouldn’t be allowed to change the struct’s data because we made it constant.
As a result, Swift makes us take an extra step: any functions that only read data are fine as they are, but any that change data belonging to the struct must be marked with a special mutating
keyword, like this:
mutating func takeVacation(days: Int) {
Now our code will build just fine, but Swift will stop us from calling takeVacation()
from constant structs.
In code, this is allowed:
var archer = Employee(name: "Sterling Archer", vacationRemaining: 14)
archer.takeVacation(days: 5)
print(archer.vacationRemaining)
But if you change var archer
to let archer
you’ll find Swift refuses to build your code again – we’re trying to call a mutating function on a constant struct, which isn’t allowed.
We’re going to explore structs in detail over the next few chapters, but first I want to give some names to things.
Album
struct, for example.Album(title: "Wings", artist: "BTS", year: 2016)
.That last one might seem a bit odd at first, because we’re treating our struct like a function and passing in parameters. This is a little bit of what’s called syntactic sugar – Swift silently creates a special function inside the struct called init()
, using all the properties of the struct as its parameters. It then automatically treats these two pieces of code as being the same:
var archer1 = Employee(name: "Sterling Archer", vacationRemaining: 14)
var archer2 = Employee.init(name: "Sterling Archer", vacationRemaining: 14)
We actually relied on this behavior previously. Way back when I introduced Double
for the first time, I explained that you can’t add an Int
and a Double
and instead need to write code like this:
let a = 1
let b = 2.0
let c = Double(a) + b
Now you can see what’s really happening here: Swift’s own Double
type is implemented as a struct, and has an initializer function that accepts an integer.
Swift is intelligent in the way it generates its initializer, even inserting default values if we assign them to our properties.
For example, if our struct had these two properties
let name: String
var vacationRemaining = 14
Then Swift will silently generate an initializer with a default value of 14 for vacationRemaining
, making both of these valid:
let kane = Employee(name: "Lana Kane")
let poovey = Employee(name: "Pam Poovey", vacationRemaining: 35)
Tip: If you assign a default value to a constant property, that will be removed from the initializer entirely. To assign a default but leave open the possibility of overriding it when needed, use a variable property.
SPONSORED Build a functional Twitter clone using APIs and SwiftUI with Stream's 7-part tutorial series. In just four days, learn how to create your own Twitter using Stream Chat, Algolia, 100ms, Mux, and RevenueCat.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.