In this chapter, you will learn the differences between structures and classes. We will start working with examples on how to code classes and customize the initialization and deinitialization of instances. We will understand how classes work as blueprints to generate instances and dive deep on all the details of automatic reference counting, also known as ARC.
You're reading from Object???Oriented Programming with Swift 2
In the previous chapter, you learned some of the basics of the object-oriented paradigm, including classes and objects, which are also known as instances. We started working on an app required by an acrylic paint manufacturer that wanted to take full advantage of the popularity of a popular YouTuber, painter, and craftswoman. We ended up creating a UML diagram with the structure of many classes, including their hierarchy, properties, and methods. It is time to take advantage of the Playground to start coding the classes and work with them.
In Swift, a class is always the type and blueprint. The object is the working instance of the class, and one or more variables can hold a reference to an instance. An object is an instance of the class, and the variables can be of a specific type (that is, a class) and hold objects of the specific blueprint that we generated when declaring the class.
It is very important to mention some of the differences...
When you ask Swift to create an instance of a specific class, something happens under the hood. Swift creates a new instance of the specified type, allocates the necessary memory, and then executes the code specified in the initializer.
Tip
You can think of initializers as equivalents of constructors in other programming languages such as C# and Java.
When Swift executes the code within an initializer, there is already a live instance of the class. Thus, we have access to the properties and methods defined in the class. However, we must be careful in the code we put in the initializer because we might end up generating huge delays when we create instances of the class.
So, for example, before you can call either the CalculateArea
or CalculatePerimeter
method, you want both the semiMajorAxis
and semiMinorAxis
fields for each new Ellipse
instance...
At some specific times, our app won't require to work with an instance anymore. For example, once you calculate the perimeter of a regular hexagon and display the results to the user, you don't need the specific RegularHexagon
instance anymore. Some programming languages require you to be careful about leaving live instances alive, and you have to explicitly destroy them and deallocate the memory that it consumed.
Swift uses an automatic reference counting, also known as ARC, to automatically deallocate the memory used by instances that aren't referenced anymore. When Swift detects that you aren't referencing an instance anymore, Swift executes the code specified within the instance's deinitializer before the instance is deallocated from memory. Thus, the deinitializer can still access all of the instance's resources.
Automatic reference counting is very easy to understand. Imagine that we have to distribute the items that we store in a box. After we distribute all the items, we must throw the box in a recycle bin. We cannot throw the box to the recycle bin when we still have one or more items in it. Seriously, we don't want to lose the items we have to distribute because they are very expensive.
The problem has a very easy solution; we just need to count the number of items that remain in the box. When the number of items in the box reaches zero, we can get rid of the box.
Tip
One or more variables can hold a reference to a single instance of a class. Thus, it is necessary to count the number of references to an instance before Swift can get rid of an instance. When the number of references to a specific instance reaches zero, Swift can automatically and safely remove the instance from memory because nobody needs this specific instance anymore.
For example, you...
The following lines declare a new minimal Circle
class in Swift:
class Circle { }
The class
keyword, followed by the class name (Circle
), composes the header of the class definition. In this case, the class doesn't have a parent class or superclass; therefore, there are neither superclasses listed after the class name, nor a colon (:
). A pair of curly braces ({}
) encloses the class body after the class header. In the forthcoming chapters, we will declare classes that inherit from another class, and therefore, they will have a superclass. In this case, the class body is empty. The Circle
class is the simplest possible class we can declare in Swift.
Tip
Any new class you create that doesn't specify a superclass is considered a base class. Whenever you declare a class without a subclass, the class doesn't inherit from a universal base class, as it happens in other programming languages such as C#. Thus, the Circle
class is known as a base class in Swift.
We want to initialize instances of the Circle
class with the radius value. In order to do so, we can take advantage of customized initializers. Initializers aren't methods, but we will write them with syntax that is very similar to the instance methods. They will use the init
keyword to differentiate from instance methods, and Swift will execute them automatically when we create an instance of a given type. Swift runs the code within the initializer before any other code within a class.
We can define an initializer that receives the radius value as an argument and use it to initialize a property with the same name. We can define as many initializers as we want to, and therefore, we can provide many different ways of initializing a class. In this case, we just need one initializer.
The following lines create a Circle
class and define an initializer within the class body:
class Circle { var radius: Double init(radius: Double) { print("I'm initializing...
We want to know when the instances of the Circle
class will be removed from memory—that is, when the objects aren't referenced by any variable and the automatic reference count mechanism decides that they have to be removed from memory. Deinitializers are special parameterless class methods that are automatically executed just before the runtime destroys an instance of a given type. Thus, we can use them to add any code we want to run before the instance is destroyed. We cannot call a deinitializer; they are only available for the runtime.
The deinitializer is a special class method that uses the deinit
keyword in its declaration. The declaration must be parameterless, and it cannot return a value.
The following lines declare a deinitializer within the body of the Circle
class:
deinit { print("I'm destroying the Circle instance with a radius value of \(radius).") }
The following lines show the new complete code for the Circle
class:
class Circle { var radius...
The following lines create an instance of the Circle
class named circle
within the scope of a getGeneratedCircleRadius
function. The code within the function uses the created instance to access and return the value of its radius property. In this case, the code uses the let
keyword to declare an immutable reference to the Circle
instance named circle
. An immutable reference is also known as a constant reference because we cannot replace the reference hold by the circle
constant to another instance of Circle
. When we use the var
keyword, we declare a reference that we can change later.
After we define the new function, we will call it. Note that the screenshot displays the results of the execution of the initializer and then the deinitializer. Swift destroys the instance after the circle
constant becomes out of scope because its reference count goes down from 1 to 0; therefore, there is no reason to keep the instance alive:
func getGeneratedCircleRadius() ...
Now that you understand an instance's life cycle, it is time to spend some time in the Playground creating new classes and instances:
Exercise 1: Create a new
Employee
class with a custom initializer that requires two string arguments:firstName
andlastName
. Use the arguments to initialize properties with the same names as the arguments. Display a message with the values forfirstName
andlastName
when an instance of the class is created. Display a message with the values forfirstName
andlastName
when an instance of the class is destroyed.Create an instance of the
Employee
class and assign it to a variable. Check the messages printed in the Playground's Debug area. Assign a new instance of theEmployee
class to the previously defined variable. Check the messages printed in the Playground's Debug area.Exercise 2: Create a function that receives two
string
arguments:firstName
andlastName
. Create an instance of the previously definedEmployee
class with the received arguments...
Swift uses one of the following mechanisms to automatically deallocate the memory used by instances that aren't referenced anymore:
Automatic Random Garbage Collector.
Automatic Reference Counting.
Automatic Instance Map Reduce.
Swift executes an instance's deinitializer:
Before the instance is deallocated from memory.
After the instance is deallocated from memory.
After the instance memory is allocated.
A deinitializer:
Can still access all of the instance's resources.
Can only access the instance's methods but no properties.
Cannot access any of the instance's resources.
Swift allows us to define:
Only one initializer per class.
A main initializer and two optional secondary initializers.
Many initializers with different arguments.
Each time we create an instance:
We must use argument labels.
We can optionally use argument labels.
We don't need to use argument labels.
In this chapter, you learned about an object's life cycle. You also learned how object initializers and deinitializers work. We declared our first class to generate a blueprint for objects. We customized object initializers and deinitializers and tested their personalized behavior in action with live examples in Swift's Playground. We understood how they work in combination with automatic reference counting.
Now that you have learned to start creating classes and instances, we are ready to share, protect, use and hide data with the data encapsulation features included in Swift, which is the topic of the next chapter.