Introduction Hello! My name is Kiryl Famin, and I am an iOS developer. Today, I want to thoroughly examine such a simple topic as initializers in Swift. Despite its apparent simplicity, sometimes the lack of a complete understanding of this topic leads to frustrating errors that one wants to fix quickly without delving into the details. In this article, we will cover everything related to initializers, including: How to retain structure’s memberwise initializer while defining a custom one Why it is not always necessary to write an initializer in classes Why calling super.init is not always required in a designated initializer Why all fields of a subclass must be populated before calling super.init How to access all parent initializers with minimal overrides in subclasses When exactly a required initializer is needed Why UIView.init() is always called without parameters, but init(frame:) and init(coder:) are overridden ...and more. But let’s take it step by step. Table of Contents Basics Structures Memberwise initializer Optionals var vs let Retaining a memberwise initializer Classes Designated initializer Convenience initializer Retaining superclass' convenience initializer Minimizing the number of overrides Compiler assistance required initializer: generics, protocols, Self(), final UIView() without parameters Honorable mentions Failable init Enums Summary Relevant links Basics Apple’s guide The Swift Programming Language (6) (which, by the way, is surprisingly detailed for initializers) states: 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. Well, i guess i don’t have to add anything here. Structures Let’s start by discussing structure initializers. This is quite simple since there is no inheritance, but there are still some rules you must know about. Memberwise initializer Let’s write a simple structure: struct BankAccount { let amount: Double let isBlocked: Bool } let bankAccount = BankAccount(amount: 735, isBlocked: Bool) Notice that we were able to initialize the structure without explicitly declaring an initializer. This happens because structures receive a memberwise initializer generated by the compiler. This works only for structures. By selecting Refactor → Generate memberwise initializer, you can see how it looks: init(amount: Double, isBlocked: Bool) { self.amount = amount self.isBlocked = isBlocked } From the signature, it’s easy to see that failing to provide values for all parameters will result in a compilation error: let bankAccount = BankAccount(amount: 735) // ❌ Missing argument for parameter 'isBlocked' in call However, if you want to reduce the number of required arguments, you can define a custom initializer: init(amount: Double, isBlocked: Bool = false) { self.amount = amount isBlocked = isBlocked } let bankAccount = BankAccount(amount: 735) // ✅ Note that if isBlocked is not populated, this will result in a compilation error because all structure properties must be populated in an initializer. Optionals, var vs let The only case where a field does not need to be populated explicitly is when it is an optional (?) variable (var). In such cases, the field will default to nil: struct BankAccount { let amount: Double var isBlocked: Bool? init(amount: Double) { self.amount = amount } } let bankAccount = BankAccount(amount: 735) // ✅ However, if we try to use the memberwise initializer in this case, we will get a compilation error: let bankAccount = BankAccount( amount: 735, isBlocked: false ) // ❌ Extra argument 'isBlocked' in call Retaining a memberwise initializer This happens because declaring a custom initializer removes the memberwise initializer. It is still possible to define it explicitly, but it will not be available automatically. However, there is a small trick to retain the memberwise initializer: declare the custom initializer in an extension. struct BankAccount { let amount: Double var isBlocked: Bool? } extension BankAccount { init(amount: Double) { self.amount = amount } } let barclaysBankAccount = BankAccount(amount: 735) // ✅ let revolutBankAccount = BankAccount(amount: 812, isBlocked: false) // ✅ print(barclaysBankAccount.isBlocked) // nil print(barclaysBankAccount.isBlocked) // false Summary for structures All fields must be populated in an initializer Optional var fields default to nil Structures receive a free memberwise initializer The memberwise initializer disappears if a custom initializer is declared To retain structure’s memberwise initializer, define a custom one in an extension Classes Designated initializer The primary initializer for a class is the designated initializer. It serves two purposes: Ensures that all fields are populated If the class is inherited, it calls the superclass initializer class Animal { var name: String init(name: String) { self.name = name } } class Dog: Animal { var breed: String var name: String init(breed: String, name: String) { self.breed = breed super.init(name: name) } } All fields must be populated before calling super.init. This is because the superclass initializer may call methods overridden by the subclass, which could access unpopulated subclass properties. class Animal { var age: Int init(age: Int) { self.age = age getInfo() } func getInfo() { print("Age: ", age) } } class Dog: Animal { var breed: String init(breed: String, age: Int) { self.breed = breed // imagine we haven't done this super.init(age: age) } override func getInfo() { print("Age: ", age, ", breed: ", breed) } } Thus, if we had not set self.breed = breed, we would have encountered a runtime error because the Animal initializer would have called the overridden getInfo() method from the Dog class. This method attempts to access the breedproperty, which would not yet be populated. Unlike structures, classes do not receive an implicit memberwise initializer. If there are uninitialized properties, a compilation error occurs: class Animal { // ❌ Class 'Animal' has no initializers var age: Int } class Animal { // ✅ var age: Int = 0 } class Animal { // ✅ var age: Int? } class Animal { } // ✅ Convenience initializer Classes can also have a convenience initializer. Unlike designated initializers, they do not create an object from scratch but simplify the initialization process by reusing the logic of other initializers. class Rectangle { var width: Double var height: Double init(width: Double, height: Double) { self.width = width self.height = height } convenience init(side: Double) { self.init(width: side, height: side) // uses a designated initializer of self } } Convenience initializers can call either designated initializers or other convenience initializers. Ultimately, a designated initializer will always be called. Convenience initializers always go horizontal (self.init), and designated initializers go vertical (super.init). Retaining Superclass’ Convenience Initializer As soon as a subclass declares new properties, it loses access to all of the superclass’s convenience initializers. class Animal { var age: Int var name: String init(age: Int, name: String) { self.age = age self.name = name } convenience init(age: Int) { self.init(age: age, name: "Default") } convenience init(name: String) { self.init(age: 0, name: name) } } class Dog: Animal { var breed: String init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } let dog = Dog(age: 3) // ❌ Missing arguments for parameters 'breed', 'name' in call This can be fixed by overriding all of the superclass’s designated initializers. class Dog: Animal { // ... override init(age: Int, name: String) { self.breed = "Mixed" super.init(age: age, name: name) } } let dog = Dog(age: 3) // ✅ It is easy to see that, in this way, to use a convenience initializer in the next subclass, you need to override two initializers. class GuideDog: Dog { var isTrained: Bool override init(age: Int, name: String) { self.isTrained = false super.init(age: age, name: name) } override init(age: Int, name: String, breed: String) { self.isTrained = false super.init(age: age, name: name, breed: breed) } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ Minimizing the number of overrides However, this can be avoided by using a convenience override initializer. class Dog: Animal { var breed: String convenience override init(age: Int, name: String) { self.init(age: age, name: name, breed: "Mixed") // self, not super } init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } class GuideDog: Dog { var isTrained: Bool // override init(age: Int, name: String) { // self.isTrained = false // // super.init(age: age, name: name, breed: "Mixed") // } convenience override init(age: Int, name: String, breed: String) { self.init(age: age, name: name, breed: breed, isTrained: false) // self, not super } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ Now we have only 2 explicitly specified initializers in each subclass. Notice how convenience override initializers call self designated init instead of super.init. This trick is thoroughly explained in Chapter 5 of Swift in Depth by Tjeerd in 't Veen, a book I highly recommend. Intermediate summary A designated initializer ensures that all properties are populated and calls super.init(). A convenience initializer simplifies initialization by calling a designated initializer. A convenience initializer becomes unavailable to subclasses if they declare new properties. To restore a superclass’s convenience initializer, all of its designated initializers must be overridden. To minimize the number of overrides, a convenience override initializer can be used. Compiler assistance We have already discussed that if a subclass does not introduce new parameters, it automatically inherits all of the superclass’s initializers. class Base { let value: Int init() { value = 0 } init(value: Int) { self.value = value } } class Subclass: Base { } let subclass = Subclass() // ✅ let subclass = Subclass(value: 3) // ✅ However, there is another important point: if the superclass has only one designated initializer and it is parameterless (init() without arguments), then explicitly declared initializers in the subclass do not need to call super.init(). In this case, the Swift compiler automatically inserts the call to the available super.init() without arguments. class Base { init() { } } class Subclass: Base { let secondValue: Int init(secondValue: Int) { self.secondValue = secondValue // ✅ without explicit super.init() } } The code compiles because super.init() is implicitly called. This is crucial for some of the following examples. Required A required initializer is used in all cases where a subclass must have the same initializer as the base class. It must also call super.init(). Below are examples where a required initializer is necessary. Generics Calling init on a generic type is only possible by declaring it as a required init. class Base { } class Subclass: Base { } struct Factory<T: Base> { func initInstance() -> T { // ❌ Constructing an object of class T() // type 'T' with a metatype value } // must use a 'required' initializer } This code does not compile because Factory does not know anything about the subclasses of Base. Although in this particular case, Subclass does have an init() without parameters, imagine if it introduced a new field: class Subclass: Base { let value: Int init(value: Int) { self.value = value } } Here, it no longer has an empty init, so it must be declared as required. class Base { required init() { } } class Subclass: Base { } struct Factory<T: Base> { static func initInstance() -> T { // ✅ T() } } let subclass = Factory<Subclass>.initInstance() Notice that even though we did not explicitly declare required init in Subclass, the compiler generated it for us. This was discussed in Compiler Assistance. The required init was automatically inherited and called super.init(). class Subclass: Base { required init() { super.init() } } Protocols All initializers declared in protocols must be required: protocol Initable { init() } class InitableObject: Initable { init() { // ❌ Initializer requirement 'init()' can only // be satisfied by a 'required' initializer } // in non-final class 'InitableObject' } Again, this is necessary so that the compiler ensures that the subclass implements the protocol initializer. As we already know, this does not always happen—if init is not required, the subclass is not obligated to override it and may define its own initializer. class IntValue: InitableObject { let value: Int init(value: Int) { self.value = value } } let InitableType: Initable.Type = IntValue.self let initable: Initable = InitableType.init() Of course, the following code will not compile because Base.init() is not required. class InitableObject: Initable { required init() { } // ✅ } class IntValue: InitableObject { let value: Int required init() { self.value = 0 } init(value: Int) { self.value = value } } Self() A similar situation occurs when calling the Self() initializer in static methods. class Base { let value: Int init(value: Int) { self.value = value } static func instantiate() -> Self { Self(value: 3) // ❌ Constructing an object of class type 'Self' } // with a metatype value must use a 'required' initializer } As always, the issue lies in inheritance: class Subclass: BaseClass { let anotherValue: Int init(anotherValue: Int) { self.anotherValue = anotherValue } } let subclass = Subclass.instantiate() // ❌ class BaseClass { let value: Int required init(value: Int) { // ✅ self.value = value } static func instantiate() -> Self { Self(value: 3) } } Getting rid of required: final Since the purpose of required is to enforce the implementation of an initializer in subclasses, naturally, prohibiting inheritance using the final keyword removes the need to mark an initializer as required. protocol Initable { init() } final class InitableObject: Initable { } // ✅ protocol ValueInitable { init(value: Int) } final class ValueInitableObject: ValueInitable { init(value: Int) { } // ✅ } Intermediate summary If a subclass does not introduce new parameters, it automatically inherits all initializers from its superclass. If the superclass only has init() without parameters, it is called automatically in subclass initializers. A required initializer is needed to guarantee its presence in subclasses for use in generics, protocols, and Self(). UIView() A brief mention of the UIView() initializer without parameters, which cannot be found in the documentation but is mysteriously used everywhere. The reason is that UIView inherits from NSObject, which has an init() without parameters. Therefore, this initializer is not explicitly declared in the UIView interface, yet it is still available: @available(iOS 2.0, *) @MainActor open class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate { open class var layerClass: AnyClass { get } public init(frame: CGRect) public init?(coder: NSCoder) open var isUserInteractionEnabled: Bool // no init() However, under the hood, this initializer calls init(frame:) when initialized in code or init(coder:) when initialized via Interface Builder. This happens because UIView provides its own implementation of NSObject.init(), which can be confirmed by the fact that method_getImplementation returns different addresses for NSObject.init() and UIView.init(). Honorable mentions Failable init A failable init is just one that returns an optional final class Failable { let positiveValue: Int init?(value: Int) { guard value > 0 else { return nil } positiveValue = value } } Enum Enums with a raw value get a free init?(rawValue:) enum Direction: String { case north case west case south case east } let north = Direction(rawValue: "north") You can also create a custom init for enums. All enum inits must assign self. enum DeviceType { case phone case tablet init(screenWidth: Int) { self = screenWidth > 800 ? .tablet : .phone } } Final summary We have covered all the essential aspects of initializers in Swift: In an initializer, all fields must be populated. Optional var properties default to nil. Structures receive a free memberwise initializer. The memberwise initializer disappears when a custom initializer is defined. A designated initializer ensures all fields are populated and calls super.init(). A convenience initializer simplifies initialization by calling a designated initializer. Convenience initializers always go horizontal (self.init), and designated initializers go vertical (super.init). A convenience initializer becomes unavailable to subclasses if they declare new properties. To restore a superclass’s convenience initializer, all of its designated initializers must be overridden. To minimize the number of overrides, a convenience override initializer can be used. If a subclass does not introduce new parameters, it automatically inherits all initializers from its superclass. If the superclass only has init() without parameters, it is automatically called in subclass initializers. A required initializer ensures its presence in subclasses for use in generics, protocols, and Self(). UIView.init() calls either UIView.init(frame:) or UIView.init(coder:). A failable initializer returns an optional. Enums with a raw value get a free init?(rawValue:). All enum initializers must assign self. I hope you found something useful in this article. If anything remains unclear or you find inaccuracy, feel free to contact me for a free explanation on Telegram: @kfamyn. Relevant links The Swift Programming Language (6) / Initialization Swift in Depth by Tjeerd in 't Veen Telegram - @kfamyn Introduction Introduction Hello! My name is Kiryl Famin, and I am an iOS developer. Today, I want to thoroughly examine such a simple topic as initializers in Swift. Despite its apparent simplicity, sometimes the lack of a complete understanding of this topic leads to frustrating errors that one wants to fix quickly without delving into the details. complete In this article, we will cover everything related to initializers, including: How to retain structure’s memberwise initializer while defining a custom one Why it is not always necessary to write an initializer in classes Why calling super.init is not always required in a designated initializer Why all fields of a subclass must be populated before calling super.init How to access all parent initializers with minimal overrides in subclasses When exactly a required initializer is needed Why UIView.init() is always called without parameters, but init(frame:) and init(coder:) are overridden How to retain structure’s memberwise initializer while defining a custom one How to retain structure’s memberwise initializer while defining a custom one Why it is not always necessary to write an initializer in classes Why it is not always necessary to write an initializer in classes Why calling super.init is not always required in a designated initializer Why calling super.init is not always required in a designated initializer super.init Why all fields of a subclass must be populated before calling super.init Why all fields of a subclass must be populated before calling super.init super.init How to access all parent initializers with minimal overrides in subclasses How to access all parent initializers with minimal overrides in subclasses When exactly a required initializer is needed When exactly a required initializer is needed required Why UIView.init() is always called without parameters, but init(frame:) and init(coder:) are overridden Why UIView.init() is always called without parameters, but init(frame:) and init(coder:) are overridden UIView.init() init(frame:) init(coder:) ...and more. But let’s take it step by step. Table of Contents Basics Basics Structures Structures Memberwise initializer Optionals var vs let Retaining a memberwise initializer Memberwise initializer Optionals var vs let var let Retaining a memberwise initializer Classes Classes Designated initializer Convenience initializer Retaining superclass' convenience initializer Minimizing the number of overrides Compiler assistance required initializer: generics, protocols, Self(), final UIView() without parameters Designated initializer Convenience initializer Retaining superclass' convenience initializer Minimizing the number of overrides Compiler assistance required initializer: generics, protocols, Self() , final required Self() final UIView() without parameters UIView() Honorable mentions Honorable mentions Failable init Enums Failable init Enums Summary Summary Relevant links Relevant links Basics Apple’s guide The Swift Programming Language (6) (which, by the way, is surprisingly detailed for initializers) states: The Swift Programming Language (6) 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. 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. Initialization 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. 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. initializers Well, i guess i don’t have to add anything here. Structures Structures Let’s start by discussing structure initializers. This is quite simple since there is no inheritance, but there are still some rules you must know about. Memberwise initializer Let’s write a simple structure: struct BankAccount { let amount: Double let isBlocked: Bool } let bankAccount = BankAccount(amount: 735, isBlocked: Bool) struct BankAccount { let amount: Double let isBlocked: Bool } let bankAccount = BankAccount(amount: 735, isBlocked: Bool) Notice that we were able to initialize the structure without explicitly declaring an initializer. This happens because structures receive a memberwise initializer generated by the compiler. This works only for structures . memberwise initializer only for structures By selecting Refactor → Generate memberwise initializer , you can see how it looks: Refactor → Generate memberwise initializer init(amount: Double, isBlocked: Bool) { self.amount = amount self.isBlocked = isBlocked } init(amount: Double, isBlocked: Bool) { self.amount = amount self.isBlocked = isBlocked } From the signature, it’s easy to see that failing to provide values for all parameters will result in a compilation error: let bankAccount = BankAccount(amount: 735) // ❌ Missing argument for parameter 'isBlocked' in call let bankAccount = BankAccount(amount: 735) // ❌ Missing argument for parameter 'isBlocked' in call However, if you want to reduce the number of required arguments, you can define a custom initializer: init(amount: Double, isBlocked: Bool = false) { self.amount = amount isBlocked = isBlocked } let bankAccount = BankAccount(amount: 735) // ✅ init(amount: Double, isBlocked: Bool = false) { self.amount = amount isBlocked = isBlocked } let bankAccount = BankAccount(amount: 735) // ✅ Note that if isBlocked is not populated, this will result in a compilation error because all structure properties must be populated in an initializer . isBlocked all structure properties must be populated in an initializer Optionals, var vs let Optionals, var let The only case where a field does not need to be populated explicitly is when it is an optional ( ? ) variable ( var ). In such cases, the field will default to nil : explicitly optional ? variable var nil struct BankAccount { let amount: Double var isBlocked: Bool? init(amount: Double) { self.amount = amount } } let bankAccount = BankAccount(amount: 735) // ✅ struct BankAccount { let amount: Double var isBlocked: Bool? init(amount: Double) { self.amount = amount } } let bankAccount = BankAccount(amount: 735) // ✅ However, if we try to use the memberwise initializer in this case, we will get a compilation error: let bankAccount = BankAccount( amount: 735, isBlocked: false ) // ❌ Extra argument 'isBlocked' in call let bankAccount = BankAccount( amount: 735, isBlocked: false ) // ❌ Extra argument 'isBlocked' in call Retaining a memberwise initializer This happens because declaring a custom initializer removes the memberwise initializer. It is still possible to define it explicitly, but it will not be available automatically. However, there is a small trick to retain the memberwise initializer: declare the custom initializer in an extension . extension struct BankAccount { let amount: Double var isBlocked: Bool? } extension BankAccount { init(amount: Double) { self.amount = amount } } let barclaysBankAccount = BankAccount(amount: 735) // ✅ let revolutBankAccount = BankAccount(amount: 812, isBlocked: false) // ✅ print(barclaysBankAccount.isBlocked) // nil print(barclaysBankAccount.isBlocked) // false struct BankAccount { let amount: Double var isBlocked: Bool? } extension BankAccount { init(amount: Double) { self.amount = amount } } let barclaysBankAccount = BankAccount(amount: 735) // ✅ let revolutBankAccount = BankAccount(amount: 812, isBlocked: false) // ✅ print(barclaysBankAccount.isBlocked) // nil print(barclaysBankAccount.isBlocked) // false Summary for structures Summary for structures All fields must be populated in an initializer Optional var fields default to nil Structures receive a free memberwise initializer The memberwise initializer disappears if a custom initializer is declared To retain structure’s memberwise initializer, define a custom one in an extension All fields must be populated in an initializer All fields must be populated in an initializer Optional var fields default to nil Optional var fields default to nil Structures receive a free memberwise initializer Structures receive a free memberwise initializer The memberwise initializer disappears if a custom initializer is declared The memberwise initializer disappears if a custom initializer is declared To retain structure’s memberwise initializer, define a custom one in an extension To retain structure’s memberwise initializer, define a custom one in an extension Classes Classes Designated initializer The primary initializer for a class is the designated initializer . It serves two purposes: designated initializer Ensures that all fields are populated If the class is inherited, it calls the superclass initializer Ensures that all fields are populated If the class is inherited, it calls the superclass initializer class Animal { var name: String init(name: String) { self.name = name } } class Dog: Animal { var breed: String var name: String init(breed: String, name: String) { self.breed = breed super.init(name: name) } } class Animal { var name: String init(name: String) { self.name = name } } class Dog: Animal { var breed: String var name: String init(breed: String, name: String) { self.breed = breed super.init(name: name) } } All fields must be populated before calling super.init . This is because the superclass initializer may call methods overridden by the subclass, which could access unpopulated subclass properties. must be populated before super.init class Animal { var age: Int init(age: Int) { self.age = age getInfo() } func getInfo() { print("Age: ", age) } } class Dog: Animal { var breed: String init(breed: String, age: Int) { self.breed = breed // imagine we haven't done this super.init(age: age) } override func getInfo() { print("Age: ", age, ", breed: ", breed) } } class Animal { var age: Int init(age: Int) { self.age = age getInfo() } func getInfo() { print("Age: ", age) } } class Dog: Animal { var breed: String init(breed: String, age: Int) { self.breed = breed // imagine we haven't done this super.init(age: age) } override func getInfo() { print("Age: ", age, ", breed: ", breed) } } Thus, if we had not set self.breed = breed , we would have encountered a runtime error because the Animal initializer would have called the overridden getInfo() method from the Dog class. This method attempts to access the breed property, which would not yet be populated. self.breed = breed Animal getInfo() Dog breed Unlike structures, classes do not receive an implicit memberwise initializer. If there are uninitialized properties, a compilation error occurs: class Animal { // ❌ Class 'Animal' has no initializers var age: Int } class Animal { // ❌ Class 'Animal' has no initializers var age: Int } class Animal { // ✅ var age: Int = 0 } class Animal { // ✅ var age: Int = 0 } class Animal { // ✅ var age: Int? } class Animal { // ✅ var age: Int? } class Animal { } // ✅ class Animal { } // ✅ Convenience initializer Classes can also have a convenience initializer . Unlike designated initializers, they do not create an object from scratch but simplify the initialization process by reusing the logic of other initializers. convenience initializer class Rectangle { var width: Double var height: Double init(width: Double, height: Double) { self.width = width self.height = height } convenience init(side: Double) { self.init(width: side, height: side) // uses a designated initializer of self } } class Rectangle { var width: Double var height: Double init(width: Double, height: Double) { self.width = width self.height = height } convenience init(side: Double) { self.init(width: side, height: side) // uses a designated initializer of self } } Convenience initializers can call either designated initializers or other convenience initializers. Ultimately, a designated initializer will always be called. Convenience initializers always go horizontal (self.init), and designated initializers go vertical (super.init). Convenience initializers always go horizontal (self.init), and designated initializers go vertical (super.init). Retaining Superclass’ Convenience Initializer Retaining Superclass’ Convenience Initializer As soon as a subclass declares new properties, it loses access to all of the superclass’s convenience initializers. class Animal { var age: Int var name: String init(age: Int, name: String) { self.age = age self.name = name } convenience init(age: Int) { self.init(age: age, name: "Default") } convenience init(name: String) { self.init(age: 0, name: name) } } class Dog: Animal { var breed: String init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } let dog = Dog(age: 3) // ❌ Missing arguments for parameters 'breed', 'name' in call class Animal { var age: Int var name: String init(age: Int, name: String) { self.age = age self.name = name } convenience init(age: Int) { self.init(age: age, name: "Default") } convenience init(name: String) { self.init(age: 0, name: name) } } class Dog: Animal { var breed: String init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } let dog = Dog(age: 3) // ❌ Missing arguments for parameters 'breed', 'name' in call This can be fixed by overriding all of the superclass’s designated initializers . all of the superclass’s designated initializers class Dog: Animal { // ... override init(age: Int, name: String) { self.breed = "Mixed" super.init(age: age, name: name) } } let dog = Dog(age: 3) // ✅ class Dog: Animal { // ... override init(age: Int, name: String) { self.breed = "Mixed" super.init(age: age, name: name) } } let dog = Dog(age: 3) // ✅ It is easy to see that, in this way, to use a convenience initializer in the next subclass, you need to override two initializers. two class GuideDog: Dog { var isTrained: Bool override init(age: Int, name: String) { self.isTrained = false super.init(age: age, name: name) } override init(age: Int, name: String, breed: String) { self.isTrained = false super.init(age: age, name: name, breed: breed) } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ class GuideDog: Dog { var isTrained: Bool override init(age: Int, name: String) { self.isTrained = false super.init(age: age, name: name) } override init(age: Int, name: String, breed: String) { self.isTrained = false super.init(age: age, name: name, breed: breed) } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ Minimizing the number of overrides Minimizing the number of overrides However, this can be avoided by using a convenience override initializer . convenience override initializer class Dog: Animal { var breed: String convenience override init(age: Int, name: String) { self.init(age: age, name: name, breed: "Mixed") // self, not super } init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } class GuideDog: Dog { var isTrained: Bool // override init(age: Int, name: String) { // self.isTrained = false // // super.init(age: age, name: name, breed: "Mixed") // } convenience override init(age: Int, name: String, breed: String) { self.init(age: age, name: name, breed: breed, isTrained: false) // self, not super } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ class Dog: Animal { var breed: String convenience override init(age: Int, name: String) { self.init(age: age, name: name, breed: "Mixed") // self, not super } init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } class GuideDog: Dog { var isTrained: Bool // override init(age: Int, name: String) { // self.isTrained = false // // super.init(age: age, name: name, breed: "Mixed") // } convenience override init(age: Int, name: String, breed: String) { self.init(age: age, name: name, breed: breed, isTrained: false) // self, not super } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ Now we have only 2 explicitly specified initializers in each subclass. Notice how convenience override initializers call self designated init instead of super.init. Notice how convenience override initializers call self designated init instead of super.init . Notice how convenience override initializers call self super.init This trick is thoroughly explained in Chapter 5 of Swift in Depth by Tjeerd in 't Veen, a book I highly recommend. Swift in Depth Intermediate summary Intermediate summary A designated initializer ensures that all properties are populated and calls super.init(). A convenience initializer simplifies initialization by calling a designated initializer. A convenience initializer becomes unavailable to subclasses if they declare new properties. To restore a superclass’s convenience initializer, all of its designated initializers must be overridden. To minimize the number of overrides, a convenience override initializer can be used. A designated initializer ensures that all properties are populated and calls super.init() . designated initializer super.init() A convenience initializer simplifies initialization by calling a designated initializer. convenience initializer A convenience initializer becomes unavailable to subclasses if they declare new properties. convenience initializer To restore a superclass’s convenience initializer , all of its designated initializers must be overridden. convenience initializer To minimize the number of overrides, a convenience override initializer can be used. convenience override initializer Compiler assistance Compiler assistance We have already discussed that if a subclass does not introduce new parameters, it automatically inherits all of the superclass’s initializers. class Base { let value: Int init() { value = 0 } init(value: Int) { self.value = value } } class Subclass: Base { } let subclass = Subclass() // ✅ let subclass = Subclass(value: 3) // ✅ class Base { let value: Int init() { value = 0 } init(value: Int) { self.value = value } } class Subclass: Base { } let subclass = Subclass() // ✅ let subclass = Subclass(value: 3) // ✅ However, there is another important point: if the superclass has only one designated initializer and it is parameterless ( init() without arguments), then explicitly declared initializers in the subclass do not need to call super.init() . In this case, the Swift compiler automatically inserts the call to the available super.init() without arguments. init() do not need super.init() automatically inserts super.init() class Base { init() { } } class Subclass: Base { let secondValue: Int init(secondValue: Int) { self.secondValue = secondValue // ✅ without explicit super.init() } } class Base { init() { } } class Subclass: Base { let secondValue: Int init(secondValue: Int) { self.secondValue = secondValue // ✅ without explicit super.init() } } The code compiles because super.init() is implicitly called. This is crucial for some of the following examples. super.init() Required A required initializer is used in all cases where a subclass must have the same initializer as the base class. It must also call super.init() . Below are examples where a required initializer is necessary. required super.init() required Generics Calling init on a generic type is only possible by declaring it as a required init . init required init class Base { } class Subclass: Base { } struct Factory<T: Base> { func initInstance() -> T { // ❌ Constructing an object of class T() // type 'T' with a metatype value } // must use a 'required' initializer } class Base { } class Subclass: Base { } struct Factory<T: Base> { func initInstance() -> T { // ❌ Constructing an object of class T() // type 'T' with a metatype value } // must use a 'required' initializer } This code does not compile because Factory does not know anything about the subclasses of Base . Although in this particular case, Subclass does have an init() without parameters, imagine if it introduced a new field: Factory Base Subclass init() class Subclass: Base { let value: Int init(value: Int) { self.value = value } } class Subclass: Base { let value: Int init(value: Int) { self.value = value } } Here, it no longer has an empty init , so it must be declared as required . init required class Base { required init() { } } class Subclass: Base { } struct Factory<T: Base> { static func initInstance() -> T { // ✅ T() } } let subclass = Factory<Subclass>.initInstance() class Base { required init() { } } class Subclass: Base { } struct Factory<T: Base> { static func initInstance() -> T { // ✅ T() } } let subclass = Factory<Subclass>.initInstance() Notice that even though we did not explicitly declare required init in Subclass , the compiler generated it for us. This was discussed in Compiler Assistance . The required init was automatically inherited and called super.init() . required init Subclass Compiler Assistance required init super.init() class Subclass: Base { required init() { super.init() } } class Subclass: Base { required init() { super.init() } } Protocols Protocols All initializers declared in protocols must be required : required protocol Initable { init() } class InitableObject: Initable { init() { // ❌ Initializer requirement 'init()' can only // be satisfied by a 'required' initializer } // in non-final class 'InitableObject' } protocol Initable { init() } class InitableObject: Initable { init() { // ❌ Initializer requirement 'init()' can only // be satisfied by a 'required' initializer } // in non-final class 'InitableObject' } Again, this is necessary so that the compiler ensures that the subclass implements the protocol initializer. As we already know, this does not always happen—if init is not required , the subclass is not obligated to override it and may define its own initializer. init required class IntValue: InitableObject { let value: Int init(value: Int) { self.value = value } } let InitableType: Initable.Type = IntValue.self let initable: Initable = InitableType.init() class IntValue: InitableObject { let value: Int init(value: Int) { self.value = value } } let InitableType: Initable.Type = IntValue.self let initable: Initable = InitableType.init() Of course, the following code will not compile because Base.init() is not required . Base.init() required class InitableObject: Initable { required init() { } // ✅ } class IntValue: InitableObject { let value: Int required init() { self.value = 0 } init(value: Int) { self.value = value } } class InitableObject: Initable { required init() { } // ✅ } class IntValue: InitableObject { let value: Int required init() { self.value = 0 } init(value: Int) { self.value = value } } Self() A similar situation occurs when calling the Self() initializer in static methods. Self() class Base { let value: Int init(value: Int) { self.value = value } static func instantiate() -> Self { Self(value: 3) // ❌ Constructing an object of class type 'Self' } // with a metatype value must use a 'required' initializer } class Base { let value: Int init(value: Int) { self.value = value } static func instantiate() -> Self { Self(value: 3) // ❌ Constructing an object of class type 'Self' } // with a metatype value must use a 'required' initializer } As always, the issue lies in inheritance: class Subclass: BaseClass { let anotherValue: Int init(anotherValue: Int) { self.anotherValue = anotherValue } } let subclass = Subclass.instantiate() // ❌ class Subclass: BaseClass { let anotherValue: Int init(anotherValue: Int) { self.anotherValue = anotherValue } } let subclass = Subclass.instantiate() // ❌ class BaseClass { let value: Int required init(value: Int) { // ✅ self.value = value } static func instantiate() -> Self { Self(value: 3) } } class BaseClass { let value: Int required init(value: Int) { // ✅ self.value = value } static func instantiate() -> Self { Self(value: 3) } } Getting rid of required : final required final Since the purpose of required is to enforce the implementation of an initializer in subclasses, naturally, prohibiting inheritance using the final keyword removes the need to mark an initializer as required . required final required protocol Initable { init() } final class InitableObject: Initable { } // ✅ protocol Initable { init() } final class InitableObject: Initable { } // ✅ protocol ValueInitable { init(value: Int) } final class ValueInitableObject: ValueInitable { init(value: Int) { } // ✅ } protocol ValueInitable { init(value: Int) } final class ValueInitableObject: ValueInitable { init(value: Int) { } // ✅ } Intermediate summary If a subclass does not introduce new parameters, it automatically inherits all initializers from its superclass. If the superclass only has init() without parameters, it is called automatically in subclass initializers. A required initializer is needed to guarantee its presence in subclasses for use in generics, protocols, and Self(). If a subclass does not introduce new parameters, it automatically inherits all initializers from its superclass. If the superclass only has init() without parameters, it is called automatically in subclass initializers. init() A required initializer is needed to guarantee its presence in subclasses for use in generics, protocols, and Self() . required Self() UIView() A brief mention of the UIView() initializer without parameters, which cannot be found in the documentation but is mysteriously used everywhere. UIView() The reason is that UIView inherits from NSObject , which has an init() without parameters. Therefore , this initializer is not explicitly declared in the UIView interface, yet it is still available: UIView NSObject init() Therefore UIView @available(iOS 2.0, *) @MainActor open class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate { open class var layerClass: AnyClass { get } public init(frame: CGRect) public init?(coder: NSCoder) open var isUserInteractionEnabled: Bool // no init() @available(iOS 2.0, *) @MainActor open class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate { open class var layerClass: AnyClass { get } public init(frame: CGRect) public init?(coder: NSCoder) open var isUserInteractionEnabled: Bool // no init() However, under the hood, this initializer calls init(frame:) when initialized in code or init(coder:) when initialized via Interface Builder. This happens because UIView provides its own implementation of NSObject.init() , which can be confirmed by the fact that method_getImplementation returns different addresses for NSObject.init() and UIView.init() . init(frame:) init(coder:) UIView NSObject.init() method_getImplementation NSObject.init() UIView.init() Honorable mentions Failable init A failable init is just one that returns an optional final class Failable { let positiveValue: Int init?(value: Int) { guard value > 0 else { return nil } positiveValue = value } } final class Failable { let positiveValue: Int init?(value: Int) { guard value > 0 else { return nil } positiveValue = value } } Enum Enums with a raw value get a free init?(rawValue:) init?(rawValue:) enum Direction: String { case north case west case south case east } let north = Direction(rawValue: "north") enum Direction: String { case north case west case south case east } let north = Direction(rawValue: "north") You can also create a custom init for enums. All enum inits must assign self . self enum DeviceType { case phone case tablet init(screenWidth: Int) { self = screenWidth > 800 ? .tablet : .phone } } enum DeviceType { case phone case tablet init(screenWidth: Int) { self = screenWidth > 800 ? .tablet : .phone } } Final summary Final summary We have covered all the essential aspects of initializers in Swift: In an initializer, all fields must be populated. Optional var properties default to nil. Structures receive a free memberwise initializer. The memberwise initializer disappears when a custom initializer is defined. A designated initializer ensures all fields are populated and calls super.init(). A convenience initializer simplifies initialization by calling a designated initializer. Convenience initializers always go horizontal (self.init), and designated initializers go vertical (super.init). A convenience initializer becomes unavailable to subclasses if they declare new properties. To restore a superclass’s convenience initializer, all of its designated initializers must be overridden. To minimize the number of overrides, a convenience override initializer can be used. If a subclass does not introduce new parameters, it automatically inherits all initializers from its superclass. If the superclass only has init() without parameters, it is automatically called in subclass initializers. A required initializer ensures its presence in subclasses for use in generics, protocols, and Self(). UIView.init() calls either UIView.init(frame:) or UIView.init(coder:). A failable initializer returns an optional. Enums with a raw value get a free init?(rawValue:). All enum initializers must assign self. In an initializer, all fields must be populated. In an initializer, all fields must be populated. Optional var properties default to nil. Optional var properties default to nil . var nil Structures receive a free memberwise initializer. Structures receive a free memberwise initializer . memberwise initializer The memberwise initializer disappears when a custom initializer is defined. The memberwise initializer disappears when a custom initializer is defined. memberwise initializer disappears A designated initializer ensures all fields are populated and calls super.init(). A designated initializer ensures all fields are populated and calls super.init() . designated initializer super.init() A convenience initializer simplifies initialization by calling a designated initializer. A convenience initializer simplifies initialization by calling a designated initializer. convenience initializer Convenience initializers always go horizontal (self.init), and designated initializers go vertical (super.init). Convenience initializers always go horizontal ( self.init ), and designated initializers go vertical ( super.init ). horizontal self.init vertical super.init A convenience initializer becomes unavailable to subclasses if they declare new properties. A convenience initializer becomes unavailable to subclasses if they declare new properties. convenience initializer To restore a superclass’s convenience initializer, all of its designated initializers must be overridden. To restore a superclass’s convenience initializer , all of its designated initializers must be overridden. convenience initializer To minimize the number of overrides, a convenience override initializer can be used. To minimize the number of overrides, a convenience override initializer can be used. convenience override initializer If a subclass does not introduce new parameters, it automatically inherits all initializers from its superclass. If a subclass does not introduce new parameters, it automatically inherits all initializers from its superclass. If the superclass only has init() without parameters, it is automatically called in subclass initializers. If the superclass only has init() without parameters, it is automatically called in subclass initializers. init() A required initializer ensures its presence in subclasses for use in generics, protocols, and Self(). A required initializer ensures its presence in subclasses for use in generics, protocols, and Self() . required initializer Self() UIView.init() calls either UIView.init(frame:) or UIView.init(coder:). UIView.init() calls either UIView.init(frame:) or UIView.init(coder:) . UIView.init() UIView.init(frame:) UIView.init(coder:) A failable initializer returns an optional. A failable initializer returns an optional. failable initializer Enums with a raw value get a free init?(rawValue:). Enums with a raw value get a free init?(rawValue:) . init?(rawValue:) All enum initializers must assign self. All enum initializers must assign self . self I hope you found something useful in this article. If anything remains unclear or you find inaccuracy, feel free to contact me for a free explanation on Telegram: @kfamyn . @kfamyn Relevant links The Swift Programming Language (6) / Initialization Swift in Depth by Tjeerd in 't Veen Telegram - @kfamyn The Swift Programming Language (6) / Initialization The Swift Programming Language (6) / Initialization Swift in Depth by Tjeerd in 't Veen Swift in Depth by Tjeerd in 't Veen Telegram - @kfamyn Telegram - @kfamyn