When can a struct’s memberwise initializer be used?
In Swift, types defined as structs automatically get a default initializer synthesized by the compiler — a so-called “memberwise initializer”, as the compiler will generate it based on the given struct’s members (that is, its stored properties).
For example, if we’ve defined a User
struct that has a name
and a preferences
property, then we can use its memberwise initialize to create instances simply by passing values for those two properties:
struct User {
var name: String
var preferences: Preferences
}
let user = User(name: "John", preferences: Preferences())
Computed properties, on the other hand, are completely ignored when the compiler synthesizes a memberwise initializer — so even if we add one, we can still keep using the above initializer just like before:
struct User {
var name: String
var preferences: Preferences
var icon: Icon { .user }
}
let user = User(name: "John", preferences: Preferences())
As of Swift 5.1, memberwise initializers also take default property values into account — meaning that if we give our preferences
property a default value, we’ll be able to create a User
instance by just passing a name
:
struct User {
var name: String
var preferences = Preferences()
}
let user = User(name: "John")
However, if a struct contains a private
property, then we’ll have to write that type’s initializer manually — in order to be able to inject a value for that property from the outside:
struct User {
var name: String
private var preferences: Preferences
init(name: String, preferences: Preferences = .init()) {
self.name = name
self.preferences = preferences
}
}
The exception to that rule, though, is that when we’re using certain property wrappers (such as SwiftUI’s State
wrapper), then we are able to make those wrapped properties private, while still being able to call the enclosing type’s memberwise initializer from outside of that type:
struct CounterView: View {
var title: String
@State private var count = 0
var body: some View {
VStack {
Text("\(title): \(count)")
Button("Increment") {
count += 1
}
}
}
}
let view = CounterView(title: "My counter")
One thing to keep in mind, though, is that memberwise initializers will never have an access level higher than internal
, which means that we can only use them internally within the module in which their type is defined.
That might initially seem like a strange restriction, but it does have merits, as we should arguably always design explicit APIs for public consumption — without making them tied to the internal structure of our data.
So, to sum up, we can use a struct’s memberwise initializer when:
- All of its members are either visible, computed, or wrapped by a wrapper that provides a default value (such as SwiftUI’s
State
). - We are creating an instance within the same module that the struct is defined in.
All other cases require us to manually implement an initializer, at least for now.