Initialization
Set the initial values for a type's stored properties and perform one-time setup.
Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that's required before the new instance is ready for use.
You implement this initialization process by defining initializers, which are like special methods that can be called to create a new instance of a particular type. Unlike Objective-C initializers, Swift initializers don't return a value. Their primary role is to ensure that new instances of a type are correctly initialized before they're used for the first time.
Instances of class types can also implement a deinitializer, which performs any custom cleanup just before an instance of that class is deallocated. For more information about deinitializers, see doc:Deinitialization.
Setting Initial Values for Stored Properties
Classes and structures must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created. Stored properties can't be left in an indeterminate state.
You can set an initial value for a stored property within an initializer, or by assigning a default property value as part of the property's definition. These actions are described in the following sections.
Note: When you assign a default value to a stored property, or set its value within an initializer, the value of that property is set directly, without calling any property observers.
Initializers
Initializers are called to create a new instance of a particular type.
In its simplest form, an initializer is like an instance method with no parameters,
written using the init keyword:
init() {
// perform some initialization here
}
The example below defines a new structure called Fahrenheit
to store temperatures expressed in the Fahrenheit scale.
The Fahrenheit structure has one stored property,
temperature, which is of type Double:
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit".
The structure defines a single initializer, init, with no parameters,
which initializes the stored temperature with a value of 32.0
(the freezing point of water in degrees Fahrenheit).
Default Property Values
You can set the initial value of a stored property from within an initializer, as shown above. Alternatively, specify a default property value as part of the property's declaration. You specify a default property value by assigning an initial value to the property when it's defined.
Note: If a property always takes the same initial value, provide a default value rather than setting a value within an initializer. The end result is the same, but the default value ties the property's initialization more closely to its declaration. It makes for shorter, clearer initializers and enables you to infer the type of the property from its default value. The default value also makes it easier for you to take advantage of default initializers and initializer inheritance, as described later in this chapter.
You can write the Fahrenheit structure from above in a simpler form
by providing a default value for its temperature property
at the point that the property is declared:
struct Fahrenheit {
var temperature = 32.0
}
Customizing Initialization
You can customize the initialization process with input parameters and optional property types, or by assigning constant properties during initialization, as described in the following sections.
Initialization Parameters
You can provide initialization parameters as part of an initializer's definition, to define the types and names of values that customize the initialization process. Initialization parameters have the same capabilities and syntax as function and method parameters.
The following example defines a structure called Celsius,
which stores temperatures expressed in degrees Celsius.
The Celsius structure implements two custom initializers called
init(fromFahrenheit:) and init(fromKelvin:),
which initialize a new instance of the structure
with a value from a different temperature scale:
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0
The first initializer has a single initialization parameter
with an argument label of fromFahrenheit and a parameter name of fahrenheit.
The second initializer has a single initialization parameter
with an argument label of fromKelvin and a parameter name of kelvin.
Both initializers convert their single argument into
the corresponding Celsius value
and store this value in a property called temperatureInCelsius.
Parameter Names and Argument Labels
As with function and method parameters, initialization parameters can have both a parameter name for use within the initializer's body and an argument label for use when calling the initializer.
However, initializers don't have an identifying function name before their parentheses in the way that functions and methods do. Therefore, the names and types of an initializer's parameters play a particularly important role in identifying which initializer should be called. Because of this, Swift provides an automatic argument label for every parameter in an initializer if you don't provide one.
The following example defines a structure called Color,
with three constant properties called red, green, and blue.
These properties store a value between 0.0 and 1.0
to indicate the amount of red, green, and blue in the color.
Color provides an initializer with
three appropriately named parameters of type Double
for its red, green, and blue components.
Color also provides a second initializer with a single white parameter,
which is used to provide the same value for all three color components.
struct Color {
let red, green, blue: Double
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
Both initializers can be used to create a new Color instance,
by providing named values for each initializer parameter:
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
Note that it isn't possible to call these initializers without using argument labels. Argument labels must always be used in an initializer if they're defined, and omitting them is a compile-time error:
let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required
Initializer Parameters Without Argument Labels
If you don't want to use an argument label for an initializer parameter,
write an underscore (_) instead of an explicit argument label for that parameter
to override the default behavior.
Here's an expanded version of the Celsius example
from doc:Initialization#Initialization-Parameters above,
with an additional initializer to create a new Celsius instance
from a Double value that's already in the Celsius scale:
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
init(_ celsius: Double) {
temperatureInCelsius = celsius
}
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0
The initializer call Celsius(37.0) is clear in its intent
without the need for an argument label.
It's therefore appropriate to write this initializer as init(_ celsius: Double)
so that it can be called by providing an unnamed Double value.
Optional Property Types
If your custom type has a stored property that's logically allowed to have “no value” ---
perhaps because its value can't be set during initialization,
or because it's allowed to have “no value” at some later point ---
declare the property with an optional type.
Properties of optional type are automatically initialized with a value of nil,
indicating that the property is deliberately intended to have “no value yet”
during initialization.
The following example defines a class called SurveyQuestion,
with an optional String property called response:
class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
The response to a survey question can't be known until it's asked,
and so the response property is declared with a type of String?,
or “optional String”.
It's automatically assigned a default value of nil, meaning “no string yet”,
when a new instance of SurveyQuestion is initialized.
Assigning Constant Properties During Initialization
You can assign a value to a constant property at any point during initialization, as long as it's set to a definite value by the time initialization finishes. Once a constant property is assigned a value, it can't be further modified.
Note: For class instances, a constant property can be modified during initialization only by the class that introduces it. It can't be modified by a subclass.
You can revise the SurveyQuestion example from above to use
a constant property rather than a variable property for the text property of the question,
to indicate that the question doesn't change once an instance of SurveyQuestion is created.
Even though the text property is now a constant,
it can still be set within the class's initializer:
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// Prints "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"
Default Initializers
Swift provides a default initializer for any structure or class that provides default values for all of its properties and doesn't provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values.
This example defines a class called ShoppingListItem,
which encapsulates the name, quantity, and purchase state
of an item in a shopping list:
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
Because all properties of the ShoppingListItem class have default values,
and because it's a base class with no superclass,
ShoppingListItem automatically gains a default initializer implementation
that creates a new instance with all of its properties set to their default values.
(The name property is an optional String property,
and so it automatically receives a default value of nil,
even though this value isn't written in the code.)
The example above uses the default initializer for the ShoppingListItem class
to create a new instance of the class with initializer syntax,
written as ShoppingListItem(),
and assigns this new instance to a variable called item.
Memberwise Initializers for Structure Types
Structure types automatically receive a memberwise initializer if they don't define any of their own custom initializers. Unlike a default initializer, the structure receives a memberwise initializer even if it has stored properties that don't have default values.
The memberwise initializer is a shorthand way to initialize the member properties of new structure instances. Initial values for the properties of the new instance can be passed to the memberwise initializer by name.
The example below defines a structure called Size
with two properties called width and height.
Both properties are inferred to be of type Double
by assigning a default value of 0.0.
The Size structure automatically receives an init(width:height:)
memberwise initializer,
which you can use to initialize a new Size instance:
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
When you call a memberwise initializer,
you can omit values for any properties
that have default values.
In the example above,
the Size structure has a default value
for both its height and width properties.
You can omit either property or both properties,
and the initializer uses the default value for anything you omit.
For example:
let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// Prints "0.0 2.0".
let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// Prints "0.0 0.0".
Initializer Delegation for Value Types
Initializers can call other initializers to perform part of an instance's initialization. This process, known as initializer delegation, avoids duplicating code across multiple initializers.
The rules for how initializer delegation works, and for what forms of delegation are allowed, are different for value types and class types. Value types (structures and enumerations) don't support inheritance, and so their initializer delegation process is relatively simple, because they can only delegate to another initializer that they provide themselves. Classes, however, can inherit from other classes, as described in doc:Inheritance. This means that classes have additional responsibilities for ensuring that all stored properties they inherit are assigned a suitable value during initialization. These responsibilities are described in doc:Initialization#Class-Inheritance-and-Initialization below.
For value types, you use self.init to refer to other initializers
from the same value type when writing your own custom initializers.
You can call self.init only from within an initializer.
Note that if you define a custom initializer for a value type, you will no longer have access to the default initializer (or the memberwise initializer, if it's a structure) for that type. This constraint prevents a situation in which additional essential setup provided in a more complex initializer is accidentally circumvented by someone using one of the automatic initializers.
Note: If you want your custom value type to be initializable with the default initializer and memberwise initializer, and also with your own custom initializers, write your custom initializers in an extension rather than as part of the value type's original implementation. For more information, see doc:Extensions.
The following example defines a custom Rect structure to represent a geometric rectangle.
The example requires two supporting structures called Size and Point,
both of which provide default values of 0.0 for all of their properties:
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
You can initialize the Rect structure below in one of three ways ---
by using its default zero-initialized origin and size property values,
by providing a specific origin point and size,
or by providing a specific center point and size.
These initialization options are represented by
three custom initializers that are part of the Rect structure's definition:
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
The first Rect initializer, init(),
is functionally the same as the default initializer that the structure would have received
if it didn't have its own custom initializers.
This initializer has an empty body,
represented by an empty pair of curly braces {}.
Calling this initializer returns a Rect instance whose
origin and size properties are both initialized with
the default values of Point(x: 0.0, y: 0.0)
and Size(width: 0.0, height: 0.0)
from their property definitions:
let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
The second Rect initializer, init(origin:size:),
is functionally the same as the memberwise initializer that the structure would have received
if it didn't have its own custom initializers.
This initializer simply assigns the origin and size argument values to
the appropriate stored properties:
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)
The third Rect initializer, init(center:size:), is slightly more complex.
It starts by calculating an appropriate origin point based on
a center point and a size value.
It then calls (or delegates) to the init(origin:size:) initializer,
which stores the new origin and size values in the appropriate properties:
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
The init(center:size:) initializer could have assigned
the new values of origin and size to the appropriate properties itself.
However, it's more convenient (and clearer in intent)
for the init(center:size:) initializer to take advantage of an existing initializer
that already provides exactly that functionality.
Note: For an alternative way to write this example without defining the
init()andinit(origin:size:)initializers yourself, see doc:Extensions.
Class Inheritance and Initialization
All of a class's stored properties --- including any properties the class inherits from its superclass --- must be assigned an initial value during initialization.
Swift defines two kinds of initializers for class types to help ensure all stored properties receive an initial value. These are known as designated initializers and convenience initializers.
Designated Initializers and Convenience Initializers
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
Classes tend to have very few designated initializers, and it's quite common for a class to have only one. Designated initializers are “funnel” points through which initialization takes place, and through which the initialization process continues up the superclass chain.
Every class must have at least one designated initializer. In some cases, this requirement is satisfied by inheriting one or more designated initializers from a superclass, as described in doc:Initialization#Automatic-Initializer-Inheritance below.
Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer's parameters set to default values. You can also define a convenience initializer to create an instance of that class for a specific use case or input value type.
You don't have to provide convenience initializers if your class doesn't require them. Create convenience initializers whenever a shortcut to a common initialization pattern will save time or make initialization of the class clearer in intent.
Syntax for Designated and Convenience Initializers
Designated initializers for classes are written in the same way as simple initializers for value types:
init(<#parameters#>) {
<#statements#>
}
Convenience initializers are written in the same style,
but with the convenience modifier placed before the init keyword,
separated by a space:
convenience init(<#parameters#>) {
<#statements#>
}
Initializer Delegation for Class Types
To simplify the relationships between designated and convenience initializers, Swift applies the following three rules for delegation calls between initializers:
term Rule 1: A designated initializer must call a designated initializer from its immediate superclass.
term Rule 2: A convenience initializer must call another initializer from the same class.
term Rule 3: A convenience initializer must ultimately call a designated initializer.
A simple way to remember this is:
- Designated initializers must always delegate up.
- Convenience initializers must always delegate across.
These rules are illustrated in the figure below:
Here, the superclass has a single designated initializer and two convenience initializers. One convenience initializer calls another convenience initializer, which in turn calls the single designated initializer. This satisfies rules 2 and 3 from above. The superclass doesn't itself have a further superclass, and so rule 1 doesn't apply.
The subclass in this figure has two designated initializers and one convenience initializer. The convenience initializer must call one of the two designated initializers, because it can only call another initializer from the same class. This satisfies rules 2 and 3 from above. Both designated initializers must call the single designated initializer from the superclass, to satisfy rule 1 from above.
Note: These rules don't affect how users of your classes create instances of each class. Any initializer in the diagram above can be used to create a fully initialized instance of the class they belong to. The rules only affect how you write the implementation of the class's initializers.
The figure below shows a more complex class hierarchy for four classes. It illustrates how the designated initializers in this hierarchy act as “funnel” points for class initialization, simplifying the interrelationships among classes in the chain:
Two-Phase Initialization
Class initialization in Swift is a two-phase process. In the first phase, each stored property is assigned an initial value by the class that introduced it. Once the initial state for every stored property has been determined, the second phase begins, and each class is given the opportunity to customize its stored properties further before the new instance is considered ready for use.
The use of a two-phase initialization process makes initialization safe, while still giving complete flexibility to each class in a class hierarchy. Two-phase initialization prevents property values from being accessed before they're initialized, and prevents property values from being set to a different value by another initializer unexpectedly.
Note: Swift's two-phase initialization process is similar to initialization in Objective-C. The main difference is that during phase 1, Objective-C assigns zero or null values (such as
0ornil) to every property. Swift's initialization flow is more flexible in that it lets you set custom initial values, and can cope with types for which0ornilisn't a valid default value.
Swift's compiler performs four helpful safety-checks to make sure that two-phase initialization is completed without error:
- term Safety check 1: A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.
As mentioned above, the memory for an object is only considered fully initialized once the initial state of all of its stored properties is known. In order for this rule to be satisfied, a designated initializer must make sure that all of its own properties are initialized before it hands off up the chain.
term Safety check 2: A designated initializer must delegate up to a superclass initializer before assigning a value to an inherited property. If it doesn't, the new value the designated initializer assigns will be overwritten by the superclass as part of its own initialization.
term Safety check 3: A convenience initializer must delegate to another initializer before assigning a value to any property (including properties defined by the same class). If it doesn't, the new value the convenience initializer assigns will be overwritten by its own class's designated initializer.
term Safety check 4: An initializer can't call any instance methods, read the values of any instance properties, or refer to
selfas a value until after the first phase of initialization is complete.
The class instance isn't fully valid until the first phase ends. Properties can only be accessed, and methods can only be called, once the class instance is known to be valid at the end of the first phase.
Here's how two-phase initialization plays out, based on the four safety checks above:
Phase 1
- A designated or convenience initializer is called on a class.
- Memory for a new instance of that class is allocated. The memory isn't yet initialized.
- A designated initializer for that class confirms that all stored properties introduced by that class have a value. The memory for these stored properties is now initialized.
- The designated initializer hands off to a superclass initializer to perform the same task for its own stored properties.
- This continues up the class inheritance chain until the top of the chain is reached.
- Once the top of the chain is reached, and the final class in the chain has ensured that all of its stored properties have a value, the instance's memory is considered to be fully initialized, and phase 1 is complete.
Phase 2
- Working back down from the top of the chain,
each designated initializer in the chain has the option to customize the instance further.
Initializers are now able to access
selfand can modify its properties, call its instance methods, and so on. - Finally, any convenience initializers in the chain have the option
to customize the instance and to work with
self.
Here's how phase 1 looks for an initialization call for a hypothetical subclass and superclass:
In this example, initialization begins with a call to a convenience initializer on the subclass. This convenience initializer can't yet modify any properties. It delegates across to a designated initializer from the same class.
The designated initializer makes sure that all of the subclass's properties have a value, as per safety check 1. It then calls a designated initializer on its superclass to continue the initialization up the chain.
The superclass's designated initializer makes sure that all of the superclass properties have a value. There are no further superclasses to initialize, and so no further delegation is needed.
As soon as all properties of the superclass have an initial value, its memory is considered fully initialized, and phase 1 is complete.
Here's how phase 2 looks for the same initialization call:
The superclass's designated initializer now has an opportunity to customize the instance further (although it doesn't have to).
Once the superclass's designated initializer is finished, the subclass's designated initializer can perform additional customization (although again, it doesn't have to).
Finally, once the subclass's designated initializer is finished, the convenience initializer that was originally called can perform additional customization.
Initializer Inheritance and Overriding
Unlike subclasses in Objective-C, Swift subclasses don't inherit their superclass initializers by default. Swift's approach prevents a situation in which a simple initializer from a superclass is inherited by a more specialized subclass and is used to create a new instance of the subclass that isn't fully or correctly initialized.
Note: Superclass initializers are inherited in certain circumstances, but only when it's safe and appropriate to do so. For more information, see doc:Initialization#Automatic-Initializer-Inheritance below.
If you want a custom subclass to present one or more of the same initializers as its superclass, you can provide a custom implementation of those initializers within the subclass.
When you write a subclass initializer that matches a superclass designated initializer,
you are effectively providing an override of that designated initializer.
Therefore, you must write the override modifier before the subclass's initializer definition.
This is true even if you are overriding an automatically provided default initializer,
as described in doc:Initialization#Default-Initializers.
As with an overridden property, method or subscript,
the presence of the override modifier prompts Swift to check that
the superclass has a matching designated initializer to be overridden,
and validates that the parameters for your overriding initializer have been specified as intended.
Note: You always write the
overridemodifier when overriding a superclass designated initializer, even if your subclass's implementation of the initializer is a convenience initializer.
Conversely, if you write a subclass initializer that matches a superclass convenience initializer,
that superclass convenience initializer can never be called directly by your subclass,
as per the rules described above in doc:Initialization#Initializer-Delegation-for-Class-Types.
Therefore, your subclass is not (strictly speaking) providing an override of the superclass initializer.
As a result, you don't write the override modifier when providing
a matching implementation of a superclass convenience initializer.