In this tutorial we cover the following topics
- Basics of object initialization
- An initializer with parameters
- Automatically provided initializers
- Designated and convenience initializers
- Failable initializers
- Required initializers
- Deinitializers
- Automatic reference counting (ARC)
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 cannot be left in an indeterminate state. So somehow we must set an initial value. It can be done by assigning a default property value (when property is defined) or within an initializer. One exception to this rule are properties of optional types whis are automatically initialized with a value of
nil
.
Object initialization in Swift, in the simplest form, looks good. When you dive into a details, you will get a headache. If you want to get it, you can read The Swift Programming Language (Swift 3.1) particularly Language Guide | Initialization
Let's start with the simplest form...
A "thing" used to create a new instance of a class is named initializer. In its simplest form it looks like an instance method with no parameters and func
keyword but written using the init
keyword as its name.
1 2 3 4 5 6 7 8 9 10 11 12 |
// An initializer - the simplest form class classA { var storedPropertyInt: Int init() { print("classA:init") storedPropertyInt = 123 } } let objClassA = classA() print(objClassA.storedPropertyInt) |
classA:init
123
Of course this type of initialization, when we can not customize it, is useless. In most cases we want to pass some values to initializer to set our properties to different values for different instances. For initializers we use the same approach to parameters names and argument labels as for functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// An initializer with parameters class classB { var storedPropertyInt: Int init(withValue value:Int) { print("Initializer with argument label") storedPropertyInt = value } init(value:Int){ print("Initializer with default argument label") storedPropertyInt = value } init(_ value:Int){ print("Initializer without a label") storedPropertyInt = value } } let objClassB_1 = classB(withValue: 3) let objClassB_2 = classB(value: 5) let objClassB_3 = classB(7) |
Initializer with argument label
Initializer with default argument label
Initializer without a label
- Default initializer Swift provides a default initializer for any structure or class that provides default values for all of its properties and does not provides at least one initializers itself. This type of initializers simply creates a new instance with all of its properties set to their default values.
- Memberwise initializer If structure does not define its own custom initializers, than it received a memberwise initializer. In contrast to a default initializer, a memberwise initializer is created even if structure has stored properties that do not have default values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class classC { var storedPropertyInt = 2 } // Default initializer let objClassC = classC() struct structA { var storedPropertyInt = 2 } struct structB { var storedPropertyInt: Int } // Default initializer let structStructA_1 = structA() // The following expression generates a compile time error: // Missing argument for parameter 'storedPropertyInt' in call // let structStructB_1 = structB() // Memberwise initializers let structStructA_2 = structA(storedPropertyInt: 3) let structStructB_2 = structB(storedPropertyInt: 3) |
This looks for me a (little bit) bizarre. If you want to have it clearly explained, once again I suggest you to read description given in The Swift Programming Language (Swift 3.1) particularly Language Guide | Initialization
Bellow I will say only a few words so you will know the most basic things.
In Swift among initializers we can distinguish two types of them
- Designated initializers. This is something that class must have -- at least one designated initializer must exist. It is used to fully initialize all properties from that class and calls an appropriate superclass initializer.
- Convenience initializers. This is an auxiliary initializer for a class and we do not have to provide it. In most cases this type of initializers is created as a shortcut to a common initialization pattern or makes initialization process clearer in intent. When exist, it must call another initializer from the same class.
In short, we use a convenience initializer to make calling a designated initializer faster and more "convenient". So convenience initializers require the use of self.init
instead of the super.init
(called in case of an override of a designated initializer). We use convenience initializers when most of initialization arguments takes a default or easy to "deduct" values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class classD { var storedPropertyInt1: Int var storedPropertyInt2: Int var storedPropertyInt3: Int // Designated initializer init(value1: Int, value2: Int, value3: Int) { storedPropertyInt1 = value1 storedPropertyInt2 = value2 storedPropertyInt3 = value3 } // Convenience initializer convenience init(baseValue: Int, multiplier: Int) { self.init(value1: baseValue, value2: baseValue*multiplier, value3: baseValue*2*multiplier); } } let objClassD_1 = classD(value1: 1, value2: 2, value3: 3) let objClassD_2 = classD(baseValue: 1, multiplier: 3) |
Sometimes, because of many different reasons, the initialization may fail. For example a required external resources may be unavailable in the time of initialization. Defining failable initializers we say Be careful, instantiation of an object of this class may fail - be prepare for this!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
class writableBinaryFile { let fileName: String init?(fileName: String) { if fileName.isEmpty { // We return nil to signal initialization failure return nil } // We have to initialize all stored properties if we want to // call method from this class. // Other case we will see the error: // Use of 'self' in method 'canCreateAFile' before // all stored properties are initialized self.fileName = fileName if !canCreateAFile(name: fileName) { return nil } //self.fileName = fileName } func canCreateAFile(name: String) -> Bool { return false } func doSomething() { print("I'm doing something now...") } } let fileOne = writableBinaryFile(fileName: "") if let file = fileOne { file.doSomething() } else { print("We do not have a writable binary file") } |
We do not have a writable binary file
If, for some reason, we want that every subclass of one class implements given initializer a
required
modifier must be used. A required
modifier must be present on all overides of a required initializer - it replaces a override
keyword.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class classE { var storedPropertyInt: Int required init() { storedPropertyInt = 123 } } class subclassOfClassE: classE { // A 'required' modifier must be present on all // overides of a required initializer required init () { // Do some required initialization } } |
A deinitializer is a piece of a code called immediately beforea class instance is deallocated (destroyed). Deinitializers are much simpler than initializers.
- There can be at most one deinitializer per class.
- We use for them
deinit
keyword and they are only available on class types. - The deinitializer does not take any arguments and is written without a perentheses.
- Deinitializers are called automatically and we are not allowed to call it directly ourself.
- Oposit to initializers, deinitializers can access all properties of the instance it is called on.
In most cases we don't need to perform manual cleanup when an instance of our calss is deallocated. However sometimes, when we are working with some strange resources, we might need to perform an extra cleanup ourself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class classG { var storedPropertyInt: Int init() { storedPropertyInt = 123 print("An initializer is called") } deinit { print("A deinitializer is called") print("Value \(storedPropertyInt) will be destroyed") } } var objClassG: classG? objClassG = classG() print("Doing something...") objClassG = nil |
An initializer is called
Doing something...
A deinitializer is called
Value 123 will be destroyed
Generally speaking, an every time we create a new instance of a class, ARC allocates a tiny part of a memory to store information about that instance. When an instance is no longer needed, ARC frees up the memory used by that instance. In most cases we don't have to take care about ARC, however in a few cases ARC requires more information about the relationships between objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class classH { var storedPropertyInt: Int init() { storedPropertyInt = 123 print("An initializer is called") } deinit { print("A deinitializer is called") } } // Create an optional pointers to classH instances var objClassHStrongReference1: classH? var objClassHStrongReference2: classH? var objClassHStrongReference3: classH? print("Create strong references...") objClassHStrongReference1 = classH() objClassHStrongReference2 = objClassHStrongReference1 objClassHStrongReference3 = objClassHStrongReference1 print("I'm breaking first strong reference") objClassHStrongReference1 = nil print("I'm breaking second strong reference") objClassHStrongReference2 = nil print("I'm breaking third strong reference") objClassHStrongReference3 = nil |
Create strong references...
An initializer is called
I'm breaking first strong reference
I'm breaking second strong reference
I'm breaking third strong reference
A deinitializer is called
Previous example works fine but it is possible to write a code in which an instance of a class "stuck" with some unwanted references. This can happen if two class instances hold a strong reference to each other: class A has a reference to class B and class B to class A (for example: parents knows its childs and childs knows its parents).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class classJ { var strongReferenceToClassK: classK? init () { print("classJ: init") } deinit { print("classJ: deinit") } } class classK { var strongReferenceToClassJ: classJ? init () { print("classK: init") } deinit { print("classK: deinit") } } var objClassJStrongReference: classJ? var objClassKStrongReference: classK? objClassJStrongReference = classJ() objClassKStrongReference = classK() objClassJStrongReference = nil objClassKStrongReference = nil // Again the same but with a cycle print("=== With a cycle ===") objClassJStrongReference = classJ() objClassKStrongReference = classK() objClassJStrongReference?.strongReferenceToClassK = objClassKStrongReference objClassKStrongReference?.strongReferenceToClassJ = objClassJStrongReference objClassJStrongReference = nil objClassKStrongReference = nil |
classJ: init
classK: init
classJ: deinit
classK: deinit
=== With a cycle ===
classJ: init
classK: init
Swift provides two ways to resolve strong reference cycles: weak references (used when the other instance has a shorter lifetime) and unowned references (used when the other instance has the same or longer lifetime).
ARC will automatically sets a weak reference to
nil
when the instance that it refers to is deallocated. The following example is identical to the previous with one exception: a weak reference is used.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class classJ { weak var strongReferenceToClassK: classK? init () { print("classJ: init") } deinit { print("classJ: deinit") } } class classK { var strongReferenceToClassJ: classJ? init () { print("classK: init") } deinit { print("classK: deinit") } } var objClassJStrongReference: classJ? var objClassKStrongReference: classK? objClassJStrongReference = classJ() objClassKStrongReference = classK() objClassJStrongReference = nil objClassKStrongReference = nil // Again the same but with a cycle print("=== With a cycle ===") objClassJStrongReference = classJ() objClassKStrongReference = classK() objClassJStrongReference?.strongReferenceToClassK = objClassKStrongReference objClassKStrongReference?.strongReferenceToClassJ = objClassJStrongReference objClassJStrongReference = nil objClassKStrongReference = nil |
classJ: init
classK: init
classJ: deinit
classK: deinit
=== With a cycle ===
classJ: init
classK: init
classK: deinit
classJ: deinit
A unowned reference is expected to always have a value. ARC never sets an unowned reference's value to
nil
. We use it only when we are sure that the reference always refers to an instance that has not been deallocated.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class classJ { unowned var strongReferenceToClassK: classK init (objClassK: classK) { strongReferenceToClassK = objClassK print("classJ: init") } deinit { print("classJ: deinit") } } class classK { var strongReferenceToClassJ: classJ? init () { print("classK: init") } deinit { print("classK: deinit") } } var objClassJStrongReference: classJ? var objClassKStrongReference: classK? objClassKStrongReference = classK() objClassJStrongReference = classJ(objClassK: objClassKStrongReference!) objClassJStrongReference = nil objClassKStrongReference = nil // Again the same but with a cycle print("=== With a cycle ===") objClassKStrongReference = classK() objClassJStrongReference = classJ(objClassK: objClassKStrongReference!) objClassKStrongReference?.strongReferenceToClassJ = objClassJStrongReference objClassJStrongReference = nil objClassKStrongReference = nil |
classK: init
classJ: init
classJ: deinit
classK: deinit
=== With a cycle ===
classK: init
classJ: init
classK: deinit
classJ: deinit
To resolve strong reference cycle between a closure and a class instance we define a capture list. This list defines the rules to use when capturing the references types within the closure's body. Capture list is given within a pair of square braces with each item separated by comma. Every item is a pairing of the
weak
or unowned
keyword with a reference to a class instance or a variable initialized with some value.
Consider the following example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class classM { var storedPropertyInt: Int // We define as a closure property rather than an instance method // to be able to replace the default implementation with a custom closure. // // We use 'lazy' to be able to refer to self within the closure - // the lazy property is not accessed until after initialization // has been completed and 'self' is known to exist. lazy var doSomethingWithInt: () -> Int = { return self.storedPropertyInt * 2 } init(integer: Int) { print("Run init") storedPropertyInt = integer } deinit { print("Run deinit") } } var objClassM1: classM? = classM(integer: 123) objClassM1 = nil var objClassM2: classM? = classM(integer: 123) print(objClassM2!.doSomethingWithInt()) objClassM2 = nil |
Run init
Run deinit
Run init
246
As long as closure is not used everything looks good (
objClassM1
) but in other case (objClassM2
) even if class could be destroyed (objClassM2 = nil
) deinitializer is not called.
To fix this, capture list must be used.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class classN { var storedPropertyInt: Int // We define as a closure property rather than an instance method // to be able to replace the default implementation with a custom closure. // // We use 'lazy' to be able to refer to self within the closure - // the lazy property is not accessed until after initialization // has been completed and 'self' is known to exist. lazy var doSomethingWithInt: () -> Int = { [unowned self] in return self.storedPropertyInt * 2 } init(integer: Int) { print("Run init") storedPropertyInt = integer } deinit { print("Run deinit") } } var objClassN1: classN? = classN(integer: 123) objClassN1 = nil var objClassN2: classN? = classN(integer: 123) print(objClassN2!.doSomethingWithInt()) objClassN2 = nil |
Run init
Run deinit
Run init
246
Run deinit
General syntax for closures with capture list is given below
1 2 3 |
{[capture list items](parameters) -> returnType in expressions } |
or simply (as we did in our case), if a closure does not specify a parameter list or return type because they can be inferred from context
1 2 3 |
{[capture list items] in expressions } |