Skip to content

Classes, part 2

Prepared and tested with Xcode 8.3 and Swift 3.1

In this tutorial we cover the following topics


Basics of object initialization

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.


classA:init
123


An initializer with parameters

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.


Initializer with argument label
Initializer with default argument label
Initializer without a label


Automatically provided initializers

  • 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.


Designated and convenience initializers

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.


Failable initializers

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!


We do not have a writable binary file


Required initializers

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.


Deinitializers

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.


An initializer is called
Doing something...
A deinitializer is called
Value 123 will be destroyed


Automatic reference counting (ARC)

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.


Automatic reference counting (ARC) in action: strong references


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


Automatic reference counting (ARC): strong reference cycles

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).


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).


A weak reference

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.


classJ: init
classK: init
classJ: deinit
classK: deinit
=== With a cycle ===
classJ: init
classK: init
classK: deinit
classJ: deinit


An unowned reference

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.


classK: init
classJ: init
classJ: deinit
classK: deinit
=== With a cycle ===
classK: init
classJ: init
classK: deinit
classJ: deinit


Strong reference cycles for closure

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


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.


Run init
Run deinit
Run init
246
Run deinit

General syntax for closures with capture list is given below

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