In this chapter, you will learn about all the elements that might compose a class. We will start organizing data in blueprints that generate instances. We will work with examples to understand how to encapsulate and hide data by working with properties combined with access control. In addition, you will learn about properties, methods, and mutable versus immutable classes.
You're reading from Object???Oriented Programming with Swift 2
So far, we worked with a very simple class and many instances of this class in the Playground. Now, it is time to dive deep into the different members of a class.
The following list enumerates the most common element types that you can include in a class definition in Swift and their equivalents in other programming languages. We already worked with a few of these elements:
Initializers: These are equivalent to constructors in other programming languages
Deinitializer: This is equivalent to destructors in other programming languages
Type properties: These are equivalent to class fields or class attributes in other programming languages
Type methods: These are equivalent to class methods in other programming languages
Instance properties: This is equivalent to instance fields or instance attributes in other programming languages
Instance methods: This is equivalent to instance functions in other programming...
When we design classes, we want to make sure that all the necessary data is available to the methods that will operate on this data; therefore, we encapsulate data. However, we just want relevant information to be visible to the users of our classes that will create instances, change values of accessible properties, and call the available methods. Thus, we want to hide or protect some data that is just needed for internal use. We don't want to make accidental changes to sensitive data.
For example, when we create a new instance of any superhero, we can use both its name and birth year as two parameters for the constructor. The constructor initializes the values of two properties: name
and birthYear
. The following lines show a sample code that declares the SuperHero
class:
class SuperHero { var name: String var birthYear: Int init(name: String, birthYear: Int) { self.name = name self.birthYear = birthYear } }
The next lines create...
As previously explained, we don't want a user of our superhero class to be able to change a superhero's birth year after an instance is initialized because the superhero won't be born again at a different date. In fact, we want to calculate and make the superhero's age available to users. We use an approximated age in order to keep the focus on the properties and don't complicate our lives with the manipulation of complete dates and the NSDate
class.
We can define a property called age
with a getter method but without a setter method; that is, we will create a read-only computed property. This way, it is possible to retrieve the superhero's age, but we cannot change it because there isn't a setter defined for the property. The getter method returns the result of calculating the superhero's age based on the current year and the value of the birthYear
stored property.
The following lines show the new version of the SuperHero
class with...
Each superhero has a running speed score that determines how fast he will move when running; therefore, we will add a public runningSpeedScore
property. We will change the initializer code to set an initial value for the new property. However, this new property has some specific requirements.
Whenever the running speed score is about to change, it will be necessary to trigger a few actions. In addition, we have to trigger other actions after the value for this property changes. We might consider adding code to a setter method combined with a related property, run a code before we set the new value to the related property, and then run a code after we set the new value. However, Swift allows us to take advantage of property observers that make it easier to run code before and after the running speed score changes.
We can define a public runningSpeedScore
property with both the willSet
and didSet
methods. After we create an instance of the new version of the...
We can define a property with a setter method that transforms the values that will be set as valid values for a related property. The getter method would just need to return the value of the related property to generate a property that will always have valid values, even when it is initialized. This way, we can make sure that whenever we require the property value, we will retrieve a valid value.
The following code replaces the previously declared runningSpeedScore
property declaration that worked with a property observer—specifically, a didSet
method. In this case, the setter transforms the values lower than 0
to 0
and values higher than 50
to 50
. The setter stores either the transformed or original value that is in a valid range in the related runningSpeedScoreField
property. The getter returns the value of the related runningSpeedScoreField
property—that is, the private property that always stores a valid value. We have to replace the previous...
So far, we worked with different type of properties. When we declare stored instance properties with the var
keyword, we create a mutable instance property, which means that we can change their values for each new instance we create. When we create an instance of a class that defines many public-stored properties, we create a mutable object, which is an object that can change its state.
For example, let's think about a class named MutableVector3D
that represents a mutable 3D vector with three public-stored properties: x
, y
, and z
. We can create a new MutableVector3D
instance and initialize the x
, y
, and z
attributes. Then, we can call the sum
method with the delta values for x
, y
, and z
as arguments. The delta values specify the difference between the existing and new or desired value. So, for example, if we specify a positive value of 30
in the deltaX
parameter, it means we want to add 30
to the X
value. The following lines declare the MutableVector3D
class that...
Mutability is very important in object-oriented programming. In fact, whenever we expose mutable properties, we create a class that will generate mutable instances. However, sometimes a mutable object can become a problem, and in certain situations, we want to avoid the objects to change their state. For example, when we work with concurrent code, an object that cannot change its state solves many concurrency problems and avoids potential bugs.
For example, we can create an immutable version of the previous MutableVector3D
class to represent an immutable 3D vector. The new ImmutableVector3D
class has three immutable instance properties declared with the let
keyword instead of the previously used var
keyword: x
, y
, and z
. We can create a new ImmutableVector3D
instance and initialize the immutable instance properties. Then, we can call the sum
method with the delta values for x
, y
, and z
as arguments.
The sum
public instance method receives the delta values for x
,...
Now that you understand instance properties, type properties, and methods, it is time to spend some time in the Playground creating new classes and instances:
Exercise 1: Create the mutable versions of the following three classes that we analyzed in Chapter 1, Objects from the Real World to Playground:
Equilateral triangle (The
EquilateralTriangle
class)Square (The
Square
class)Regular hexagon (The
RegularHexagon
class)
Exercise 2: Create the immutable versions of the previously created classes
You use the
static var
keywords to declare a:Type property.
Instance property.
Read-only computed instance property.
You use the
static let
keywords to declare a:Mutable type property.
Immutable instance property.
Immutable type property.
An instance-stored property:
Has its own and independent value for each instance of a class.
Has the same value for all the instances of a class.
Has the same value for all the instances of a class, unless it is accessed through the class name followed by dot and the property name.
A class that exposes mutable properties will:
Generate immutable instances.
Generate mutable instances.
Generate mutable classes but immutable instances.
An instance method:
Cannot access instance properties.
Can access instance properties.
Can access only type properties.
In this chapter, you learned about the different members of a class or blueprint. We worked with instance properties, type properties, instance methods, and type methods. We worked with stored properties, getters, setters, and property observers, and we took advantage of access modifiers to hide data.
We worked with superheroes and defined the shared properties of a specific type of lion superhero using type properties. We also worked with mutable and immutable versions of a 3D vector. You also understood the difference between mutable and immutable classes.
Now that you have learned to encapsulate data with properties, you are ready to create class hierarchies to abstract and specialize behavior, which is the topic of the next chapter.