< How to take action when a property changes | How to limit access to internal data using access control > |
Updated for Xcode 14.2
Initializers are specialized methods that are designed to prepare a new struct instance to be used. You’ve already seen how Swift silently generates one for us based on the properties we place inside a struct, but you can also create your own as long as you follow one golden rule: all properties must have a value by the time the initializer ends.
Let’s start by looking again at Swift’s default initializer for structs:
struct Player {
let name: String
let number: Int
}
let player = Player(name: "Megan R", number: 15)
That creates a new Player
instance by providing values for its two properties. Swift calls this the memberwise initializer, which is a fancy way of saying an initializer that accepts each property in the order it was defined.
Like I said, this kind of code is possible because Swift silently generates an initializer accepting those two values, but we could write our own to do the same thing. The only catch here is that you must be careful to distinguish between the names of parameters coming in and the names of properties being assigned.
Here’s how that would look:
struct Player {
let name: String
let number: Int
init(name: String, number: Int) {
self.name = name
self.number = number
}
}
That works the same as our previous code, except now the initializer is owned by us so we can add extra functionality there if needed.
However, there are a couple of things I want you to notice:
func
keyword. Yes, this looks like a function in terms of its syntax, but Swift treats initializers specially.Player
instance, initializers never explicitly have a return type – they always return the type of data they belong to. self
to assign parameters to properties to clarify we mean “assign the name
parameter to my name
property”.That last point is particularly important, because without self
we’d have name = name
and that doesn’t make sense – are we assigning the property to the parameter, assigning the parameter to itself, or something else? By writing self.name
we’re clarifying we mean “the name
property that belongs to my current instance,” as opposed to anything else.
Of course, our custom initializers don’t need to work like the default memberwise initializer Swift provides us with. For example, we could say that you must provide a player name, but the shirt number is randomized:
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)
Just remember the golden rule: all properties must have a value by the time the initializer ends. If we had not provided a value for number
inside the initializer, Swift would refuse to build our code.
Important: Although you can call other methods of your struct inside your initializer, you can’t do so before assigning values to all your properties – Swift needs to be sure everything is safe before doing anything else.
You can add multiple initializers to your structs if you want, as well as leveraging features such as external parameter names and default values. However, as soon as you implement your own custom initializers you’ll lose access to Swift’s generated memberwise initializer unless you take extra steps to retain it. This isn’t an accident: if you have a custom initializer, Swift effectively assumes that’s because you have some special way to initialize your properties, which means the default one should no longer be available.
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.