Classes and objects
A class describes what something looks like; an object (also called an instance) is one real thing built from that description. Defining Dog doesn't give you any dogs yet. To get one, you call the class name like a function, which runs the class's initializer:
let rex = Dog()
Here Dog() creates a fresh Dog and let rex declares a constant that holds a reference to it. Because Dog is a class, that constant refers to one object in memory; the constant fixes which object, not what is inside it.
Many objects from one class
Every call creates a new, independent object:
class Dog {
}
let rex = Dog()
let buddy = Dog()
let luna = Dog()
print(rex === buddy) // false
let alias = rex
print(alias === rex) // true
The first comparison prints false, because rex and buddy are two objects. The second prints true: alias = rex doesn't create a copy, it makes a second name for the same object. Classes are reference types, so assigning one to another constant shares the object. Once the classes get fields, a change made through rex is also visible through alias, but not through buddy.
The exercise and the optional type
The starter code ends with three checks of the form if rex != nil { print("rex exists") }. For a check against nil to make sense, the constant must be an optional, a type that can hold either a value or nil. Optional types are written with a question mark, so the lesson declares each dog as Dog?:
class Dog {
}
class Solution {
func mainFunction() {
let rex: Dog? = Dog()
let buddy: Dog? = Dog()
let luna: Dog? = Dog()
if rex != nil { print("rex exists") }
if buddy != nil { print("buddy exists") }
if luna != nil { print("luna exists") }
}
}
Running mainFunction prints rex exists, buddy exists and luna exists, one per line. Each constant was given a real Dog, so none is nil.
If you write let rex = Dog() without the Dog? annotation, the type is plain Dog, which can never be nil. The comparison still runs and still prints, but the compiler warns comparing non-optional value of type 'Dog' to 'nil' always returns true. That is why the lesson spells out the optional type.
What the initializer does
An initializer is the code that sets an object up when it is created. For an empty class, Swift supplies one for you, which is why Dog() works without any extra code. The lessons on fields and constructors show how an object gets its starting data; for now it is enough to know that the parentheses in Dog() are a call, and that the call returns a brand-new object every time.
Because each constant holds a reference, the constants themselves are small. Three constants for three dogs means three separate objects, and none of them knows about the others.
Common mistakes
- Forgetting the parentheses.
Dogalone is the type itself;Dog()creates an object. - Thinking
letfreezes the object. It only stops you from pointing the constant at a different object. Properties declared withvarcan still change. - Expecting a copy. Assigning one class constant to another shares the object. Use a new
Dog()when you want a separate one. - Using a name before declaring it. The three constants must be declared before the
iflines that use them.
Key takeaways
- Call the class name with parentheses,
Dog(), to create an object. - Each call makes a new, independent object.
- Classes are reference types:
===tells whether two names point to the same object. - A value can be compared with
nilin a meaningful way only if its type is optional, such asDog?.
