An initializer establishes the state that makes an instance usable. It should not leave callers wondering whether required fields exist or whether a basic invariant already failed. Swift checks initialization paths, and your API design determines how expected invalid input is reported.
The classes lesson introduces basic init syntax. This reference explains failable construction and the additional rules for class inheritance. A value type can often validate all its properties directly; a class hierarchy must coordinate its own stored properties with those inherited from a superclass.
Failure can be part of a construction contract
Init? returns an optional instance and uses return nil when construction should fail. This fits expected invalid input such as a blank identifier or an out-of-range quantity. A throwing initializer is another choice when callers need a descriptive reason rather than a simple absence.
Precondition is different: it expresses a programmer contract and can terminate execution when violated. Do not use it as the only response to ordinary user mistakes. Choose a failure model according to the caller's needs, then keep every successful instance valid under that model.
Stored properties must have initial values before initialization finishes. Default property values can satisfy some requirements. Let properties can be assigned during the type's initialization but cannot later be changed to repair an incomplete object. Prefer validated construction to a convention telling callers to remember a separate setup method.
Designated and convenience initializers
A designated class initializer initializes the class's own stored properties and delegates upward to an appropriate superclass designated initializer when needed. A convenience initializer provides an alternative entry path and delegates across to another initializer in the same class, eventually reaching a designated path.
This division lets you centralize the essential setup while offering useful defaults. A convenience initializer should not duplicate all property validation independently, because the two paths can drift. It selects arguments and delegates to the central path, preserving one construction policy.
Value types have initializer delegation too, but do not use the class convenience modifier. Adding a custom initializer in a struct's main declaration can affect synthesized initializers. If preserving a useful synthesized memberwise initializer matters, an extension may be appropriate; verify the access rules and available calls rather than assuming synthesis survives every change.
Two-phase initialization prevents premature use
Class initialization first establishes initial values for stored properties through the hierarchy. After that foundation is complete, further customization can occur. Swift's safety checks prevent using self in ways that assume a fully initialized instance before the required properties and superclass setup are complete.
A subclass must initialize its own introduced stored properties before delegating to the superclass initializer in the required designated path. It must also respect when inherited properties can be customized. Treat compiler errors here as evidence about the hierarchy's setup order rather than silencing them with arbitrary default values that weaken the model.
Required marks an initializer obligation for subclasses. A subclass implementation uses required as well; the modifier communicates that future subclasses must provide the path unless initializer inheritance already satisfies the rule. Overriding an ordinary superclass designated initializer generally needs override. Initializer inheritance is governed by specific rules and is not simply identical to method inheritance.
Review every available construction path after adding a stored property to a subclass. A previously inherited initializer can become unavailable or fail to express the new requirement. Compile representative caller code and test the successful invariant through each supported path. This is especially useful in a public hierarchy, where an initializer name can remain familiar while its actual inheritance and accessibility have changed.
Official references
Read the Swift initialization guide and its official source. For each initializer, identify its successful invariant, expected failure behavior and delegation destination. This review catches duplicated validation and partially configured instances before a hierarchy makes them harder to reason about.
Swift Initialization Beyond the Basics Examples
Each example shows the code, the output it produces and what each part does.
Build only acceptable quantities
Every successfully created Quantity satisfies the range rule. Optional binding makes failure visible at the call site. The wrapper also distinguishes a validated business quantity from an arbitrary Int, which can make later function signatures communicate their requirements more clearly.
struct Quantity {
let value: Int
init?(_ value: Int) {
guard (1...50).contains(value) else { return nil }
self.value = value
}
}
if let quantity = Quantity(12) {
print(quantity.value)
}
print(Quantity(0) == nil)
print(Quantity(51) == nil)12
true
trueDelegate a default class construction path
The convenience path does not create a second object after construction. It delegates initialization of this instance. When extending the hierarchy, keep the distinction between delegating to self and delegating to super explicit.
class Label {
let text: String
init(text: String) {
self.text = text
}
convenience init() {
self.init(text: "Untitled")
}
}
print(Label().text)
print(Label(text: "Stock").text)Untitled
StockSwift Initialization Beyond the Basics in Real Projects
Worked exercise: Validate a product before publication
Accept a trimmed nonblank product code and a quantity from one through fifty. Use a failable initializer and store immutable properties. This is a value model, so it needs no designated/convenience class hierarchy or separate setup call.
import Foundation
struct Product {
let code: String
let quantity: Int
init?(code: String, quantity: Int) {
let cleaned = code.trimmingCharacters(in: .whitespacesAndNewlines)
guard !cleaned.isEmpty, (1...50).contains(quantity) else { return nil }
self.code = cleaned
self.quantity = quantity
}
}
if let product = Product(code: " PEN ", quantity: 4) {
print("\(product.code):\(product.quantity)")
}
print(Product(code: " ", quantity: 4) == nil)
print(Product(code: "PEN", quantity: -1) == nil)PEN:4
true
trueTest both ends of the accepted range and the blank-code policy. If callers must display different error messages for a blank code and an invalid quantity, replace optional failure with a small throwing error type. Do not add a false successful default that hides the bad input and creates an unintended product.
Swift Initialization Beyond the Basics FAQ
When should I use init? instead of throwing init?
Use failable initialization when callers only need to know whether a valid instance exists, such as a compact parsing or lookup boundary. Use throwing initialization when the caller needs a meaningful reason or recovery decision. Both can preserve valid successful instances. Avoid forcing one style everywhere: a form may benefit from separate error messages, while a pipeline filtering invalid tokens may naturally use optional results.
Is a convenience initializer an ordinary factory function?
It is an initializer path that delegates within the same class and ultimately reaches designated initialization. A factory function is an ordinary function and can return different types or reuse an existing instance according to its contract. Choose convenience init when the operation is another way to initialize this class. Choose a factory when selection, caching or a different returned abstraction is central to the behavior.
Why cannot I call a method before super.init finishes?
The instance may not yet have a complete valid state across its class hierarchy. Swift's two-phase rules prevent methods or self-dependent operations from observing partially initialized properties. Initialize the required stored values and complete the appropriate delegation first, then perform permitted customization. If setup requires a calculation, use arguments or a helper that does not depend on the incomplete instance rather than exposing self prematurely.
Does required mean every subclass must rewrite identical code?
It means the initializer must remain available under the language's subclass rules. In some cases initializer inheritance already satisfies the requirement; in others the subclass must implement a required path. Do not confuse the obligation with a demand to duplicate every line. Delegate to a central valid initialization policy where possible, and review how new stored properties change inherited initializer availability when extending the hierarchy.
Swift Initialization Beyond the Basics Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
A form needs to distinguish an empty code from an invalid quantity. Which construction API is useful?
Swift Initialization Beyond the Basics Quiz
2 questions to check what you learned.
What does init? communicate to its caller?
- Every stored property becomes optional.
- The instance is always created and may be repaired later.
- Construction can return nil instead of a valid instance.
Where does a class convenience initializer delegate?
- To another initializer in the same class, eventually reaching designated initialization.
- Directly to any unrelated class instance.
- Only to a setter called after initialization.