In Chapter 2, Building Blocks – Variables, Collections, and Flow Control, we developed a very simple program that helped organize a party. Even though we separated parts of the code in a logical way, everything was written in a single file and our functions were all lumped together. As projects grow in complexity, this way of organizing code is not sustainable. In the same way we use functions to separate out logical components in our code at scale, we also need to be able to separate out the logical components of our functions and data. To do this, we can define code in different files and we can also create our own types that contain custom data and functionality. These types are commonly referred to as objects, as a part of the programming technique called object-oriented programming. In this chapter we will cover the following:
The most basic way that we can group together data and functionality into a logical unit or object is to define something called a structure. Essentially, a structure is a named collection of data and functions. Actually, we have already seen several different structures because all of the types such as string, array, and dictionary that we have seen so far are structures. Now we will learn how to create our own.
Let's jump straight into defining our first structure to represent a contact:
Initializing is the formal name for creating a new instance. We initialize a new Contact
like this:
You may have noticed that this looks a lot like calling a function and that is because it is very similar. Every type must have at least one special function called an
initializer. As the name implies, this is a function that initializes a new instance of the type. All initializers are named after their type and they may or may not have parameters, just like a function. In our case, we have not provided any parameters so the first and last names will be left with the default values that we provided in our specification: First
and Last
.
If we define a second contact structure that does not provide default values, it changes how we call the initializer. Since there are no default values, we must provide the values when initializing it:
The two variables, firstName
and lastName
, are called
member variables and, if we change them to be constants, they are then called
member constants. This is because they are pieces of information associated with a specific instance of the type. You can access member constants and variables on any instance of a structure:
This is in contrast to a static constant. We could add a static constant to our type by adding the following line to its definition:
Member and static constants and variables all fall under the category of properties. A property is simply a piece of information associated with an instance or a type. This helps reinforce the idea that every type is an object. A ball, for example, is an object that has many properties including its radius, color, and elasticity. We can represent a ball in code in an object-oriented way by creating a ball structure that has each of those properties:
Note that this Ball
type does not define default values for its properties. If default values are not provided in the declaration, they are required when initializing an instance of the type. This means that an empty initializer is not available for that type. If you try to use one, you will get an error:
Just like with normal variables and constants, all properties must have a value once initialized.
Just as you can define constants and variables within a structure, you can also define member and static functions. These functions are referred to as methods to distinguish them from global functions that are not associated with any type. You declare member methods in a similar way to functions but you do so inside the type declaration, as shown:
In order for a method to modify self
, it must be declared as a
mutating method using the mutating
keyword:
We can define static properties that apply to the type itself but we can also define
static methods that operate on the type by using the static
keyword. We can add a static method to our Contact
structure that prints the available phone prefixes, as shown here:
I recommend avoiding this feature of Swift. I want to make you aware of it so you are not confused when looking at other people's code but I feel that always using self
greatly increases the readability of your code. self
makes it instantly clear that the variable is attached to the instance instead of only defined in the function. You could also create bugs if you add code that creates a variable that hides a member variable. For example, you would create a bug if you introduced the firstName
variable to the printFullName
method in the preceding code without realizing you were using firstName
to access the member variable later in the code. Instead of accessing the member variable, the later code would start to only access the local variable.
So far, it seems that properties are used to store information and methods are used to perform calculations. While this is generally true, Swift has a feature called computed properties. These are properties that are calculated every time they are accessed. To do this, you define a property and then provide a method called a getter that returns the calculated value, as shown:
You can even provide a second function called a setter that allows you to assign a value to this property like normal properties:
This provides a nice concise way of defining read-only computed properties.
It is pretty common to need to perform an action whenever a property is changed. One way to achieve this is to define a computed property with a setter that performs the necessary action. However, Swift provides a better way of doing this. You can define a willSet
function or a didSet
function on any stored property. WillSet
is called just before the property is changed and it is provided with a variable newValue
. didSet
is called just after the property is changed and it is provided with a variable oldValue
, as you can see here:
In this scenario, if you set the radius
, it triggers a change on the diameter
which triggers another change on the radius
and that then continues on forever.
You may also have realized that there is another way that we have interacted with a structure in the past. We have used square brackets ([]
) with both arrays and dictionaries to access elements. These are called
subscripts and we can use them on our custom types as well. The syntax for them is similar to the computed properties that we saw before except that you define it more like a method with parameters and a return type, as you can see here:
You may have noticed a question mark (?
) in the return type. This is called an
optional and we will discuss this more in the next chapter. For now, you only need to know that this is the type that is returned when accessing a dictionary by key because a value does not exist for every possible key.
Just like with computed properties, you can define a subscript as read-only without using the get
syntax:
If you are not satisfied with the default initializers provided to you, you can define your own. This is done using the init
keyword, as shown:
This is a great tool for reducing duplicate code in multiple initializers. However, when using this, there is an extra rule that you must follow. You cannot access self
before calling the other initializer:
This guarantees that all the properties have a valid value before any method is called.
Structures are an incredibly powerful tool in programming. They are an important way that we, as programmers, can abstract away more complicated concepts. As we discussed in Chapter 2, Building Blocks – Variables, Collections, and Flow Control, this is the way we get better at using computers. Other people can provide these abstractions to us for concepts that we don't understand yet or in circumstances where it isn't worth our time to start from scratch. We can also use these abstractions for ourselves so that we can better understand the high-level logic going on in our app. This will greatly increase the reliability of our code. Structures make our code more understandable both for other people and for ourselves in the future.
A class can do everything that a structure can do except that a class can use something called inheritance. A class can inherit the functionality from another class and then extend or customize its behavior. Let's jump right into some code.
Firstly, let's define a class called Building
that we can inherit from later:
Predictably, a class is defined using the class
keyword instead of struct
. Otherwise, a class looks extremely similar to a structure. However, we can also see one difference. With a structure, the initializer we created before would not be necessary because it would have been created for us. With classes, initializers are not automatically created unless all of the properties have default values.
Now let's look at how to inherit from this building class:
Here, we have created a new class called House
that inherits from our Building
class. This is denoted by the colon (:
) followed by Building
in the class declaration. Formally, we would say that House
is a
subclass of Building
and Building
is a
superclass of House
.
The trunk of the tree is the topmost superclass and each subclass is a separate branch off of that. The topmost superclass is commonly referred to as the base class as it forms the foundation for all the other classes.
Because of the hierarchical nature of classes, the rules for their initializers are more complex. The following additional rules are applied:
Inheritance also creates four types of initializers shown here:
A
required initializer is a type of initializer for superclasses. If you mark an initializer as required, it forces all of the subclasses to also define that initializer. For example, we could make the Building
initializer required, as shown:
Then, if we implemented our own initializer in House
, we would get an error like this:
This time, when declaring this initializer, we repeat the required
keyword instead of using override
:
To discuss designated initializers, we first have to talk about convenience initializers. The normal initializer that we started with is really called a designated initializer. This means that they are core ways to initialize the class. You can also create convenience initializers which, as the name suggests, are there for convenience and are not a core way to initialize the class.
All convenience initializers must call a designated initializer and they do not have the ability to manually initialize properties like a designated initializer does. For example, we can define a convenience initializer on our Building
class that takes another building and makes a copy:
Now, as a convenience, you can create a new building using the properties from an existing building. The other rule about convenience initializers is that they cannot be used by a subclass. If you try to do that, you will get an error like this:
This is one of the main reasons that convenience initializers exist. Ideally, every class should only have one designated initializer. The fewer designated initializers you have, the easier it is to maintain your class hierarchy. This is because you will often add additional properties and other things that need to be initialized. Every time you add something like that, you will have to make sure that every designated initializer sets things up properly and consistently. Using a convenience initializer instead of a designated initializer ensures that everything is consistent because it must call a designated initializer that, in turn, is required to set everything up properly. Basically, you want to funnel all of your initialization through as few designated initializers as possible.
Just as with initializers, subclasses can override methods and computed properties. However, you have to be more careful with these. The compiler has fewer protections.
Even though it is possible, there is no requirement that an overriding method calls its superclass implementation. For example, let's add clean methods to our Building
and House
classes:
This is a great example of the need to override methods. We can provide common functionality in a superclass that can be extended in each of its subclasses instead of rewriting the same functionality in multiple classes.
We have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
define a class called Building
that we can inherit from later:
Predictably, a class is defined using the class
keyword instead of struct
. Otherwise, a class looks extremely similar to a structure. However, we can also see one difference. With a structure, the initializer we created before would not be necessary because it would have been created for us. With classes, initializers are not automatically created unless all of the properties have default values.
Now let's look at how to inherit from this building class:
Here, we have created a new class called House
that inherits from our Building
class. This is denoted by the colon (:
) followed by Building
in the class declaration. Formally, we would say that House
is a
subclass of Building
and Building
is a
superclass of House
.
The trunk of the tree is the topmost superclass and each subclass is a separate branch off of that. The topmost superclass is commonly referred to as the base class as it forms the foundation for all the other classes.
Because of the hierarchical nature of classes, the rules for their initializers are more complex. The following additional rules are applied:
Inheritance also creates four types of initializers shown here:
A
required initializer is a type of initializer for superclasses. If you mark an initializer as required, it forces all of the subclasses to also define that initializer. For example, we could make the Building
initializer required, as shown:
Then, if we implemented our own initializer in House
, we would get an error like this:
This time, when declaring this initializer, we repeat the required
keyword instead of using override
:
To discuss designated initializers, we first have to talk about convenience initializers. The normal initializer that we started with is really called a designated initializer. This means that they are core ways to initialize the class. You can also create convenience initializers which, as the name suggests, are there for convenience and are not a core way to initialize the class.
All convenience initializers must call a designated initializer and they do not have the ability to manually initialize properties like a designated initializer does. For example, we can define a convenience initializer on our Building
class that takes another building and makes a copy:
Now, as a convenience, you can create a new building using the properties from an existing building. The other rule about convenience initializers is that they cannot be used by a subclass. If you try to do that, you will get an error like this:
This is one of the main reasons that convenience initializers exist. Ideally, every class should only have one designated initializer. The fewer designated initializers you have, the easier it is to maintain your class hierarchy. This is because you will often add additional properties and other things that need to be initialized. Every time you add something like that, you will have to make sure that every designated initializer sets things up properly and consistently. Using a convenience initializer instead of a designated initializer ensures that everything is consistent because it must call a designated initializer that, in turn, is required to set everything up properly. Basically, you want to funnel all of your initialization through as few designated initializers as possible.
Just as with initializers, subclasses can override methods and computed properties. However, you have to be more careful with these. The compiler has fewer protections.
Even though it is possible, there is no requirement that an overriding method calls its superclass implementation. For example, let's add clean methods to our Building
and House
classes:
This is a great example of the need to override methods. We can provide common functionality in a superclass that can be extended in each of its subclasses instead of rewriting the same functionality in multiple classes.
We have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
of the hierarchical nature of classes, the rules for their initializers are more complex. The following additional rules are applied:
Inheritance also creates four types of initializers shown here:
A
required initializer is a type of initializer for superclasses. If you mark an initializer as required, it forces all of the subclasses to also define that initializer. For example, we could make the Building
initializer required, as shown:
Then, if we implemented our own initializer in House
, we would get an error like this:
This time, when declaring this initializer, we repeat the required
keyword instead of using override
:
To discuss designated initializers, we first have to talk about convenience initializers. The normal initializer that we started with is really called a designated initializer. This means that they are core ways to initialize the class. You can also create convenience initializers which, as the name suggests, are there for convenience and are not a core way to initialize the class.
All convenience initializers must call a designated initializer and they do not have the ability to manually initialize properties like a designated initializer does. For example, we can define a convenience initializer on our Building
class that takes another building and makes a copy:
Now, as a convenience, you can create a new building using the properties from an existing building. The other rule about convenience initializers is that they cannot be used by a subclass. If you try to do that, you will get an error like this:
This is one of the main reasons that convenience initializers exist. Ideally, every class should only have one designated initializer. The fewer designated initializers you have, the easier it is to maintain your class hierarchy. This is because you will often add additional properties and other things that need to be initialized. Every time you add something like that, you will have to make sure that every designated initializer sets things up properly and consistently. Using a convenience initializer instead of a designated initializer ensures that everything is consistent because it must call a designated initializer that, in turn, is required to set everything up properly. Basically, you want to funnel all of your initialization through as few designated initializers as possible.
Just as with initializers, subclasses can override methods and computed properties. However, you have to be more careful with these. The compiler has fewer protections.
Even though it is possible, there is no requirement that an overriding method calls its superclass implementation. For example, let's add clean methods to our Building
and House
classes:
This is a great example of the need to override methods. We can provide common functionality in a superclass that can be extended in each of its subclasses instead of rewriting the same functionality in multiple classes.
We have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
A
required initializer is a type of initializer for superclasses. If you mark an initializer as required, it forces all of the subclasses to also define that initializer. For example, we could make the Building
initializer required, as shown:
Then, if we implemented our own initializer in House
, we would get an error like this:
This time, when declaring this initializer, we repeat the required
keyword instead of using override
:
To discuss designated initializers, we first have to talk about convenience initializers. The normal initializer that we started with is really called a designated initializer. This means that they are core ways to initialize the class. You can also create convenience initializers which, as the name suggests, are there for convenience and are not a core way to initialize the class.
All convenience initializers must call a designated initializer and they do not have the ability to manually initialize properties like a designated initializer does. For example, we can define a convenience initializer on our Building
class that takes another building and makes a copy:
Now, as a convenience, you can create a new building using the properties from an existing building. The other rule about convenience initializers is that they cannot be used by a subclass. If you try to do that, you will get an error like this:
This is one of the main reasons that convenience initializers exist. Ideally, every class should only have one designated initializer. The fewer designated initializers you have, the easier it is to maintain your class hierarchy. This is because you will often add additional properties and other things that need to be initialized. Every time you add something like that, you will have to make sure that every designated initializer sets things up properly and consistently. Using a convenience initializer instead of a designated initializer ensures that everything is consistent because it must call a designated initializer that, in turn, is required to set everything up properly. Basically, you want to funnel all of your initialization through as few designated initializers as possible.
Just as with initializers, subclasses can override methods and computed properties. However, you have to be more careful with these. The compiler has fewer protections.
Even though it is possible, there is no requirement that an overriding method calls its superclass implementation. For example, let's add clean methods to our Building
and House
classes:
This is a great example of the need to override methods. We can provide common functionality in a superclass that can be extended in each of its subclasses instead of rewriting the same functionality in multiple classes.
We have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
required initializer is a type of initializer for superclasses. If you mark an initializer as required, it forces all of the subclasses to also define that initializer. For example, we could make the Building
initializer required, as shown:
Then, if we implemented our own initializer in House
, we would get an error like this:
This time, when declaring this initializer, we repeat the required
keyword instead of using override
:
To discuss designated initializers, we first have to talk about convenience initializers. The normal initializer that we started with is really called a designated initializer. This means that they are core ways to initialize the class. You can also create convenience initializers which, as the name suggests, are there for convenience and are not a core way to initialize the class.
All convenience initializers must call a designated initializer and they do not have the ability to manually initialize properties like a designated initializer does. For example, we can define a convenience initializer on our Building
class that takes another building and makes a copy:
Now, as a convenience, you can create a new building using the properties from an existing building. The other rule about convenience initializers is that they cannot be used by a subclass. If you try to do that, you will get an error like this:
This is one of the main reasons that convenience initializers exist. Ideally, every class should only have one designated initializer. The fewer designated initializers you have, the easier it is to maintain your class hierarchy. This is because you will often add additional properties and other things that need to be initialized. Every time you add something like that, you will have to make sure that every designated initializer sets things up properly and consistently. Using a convenience initializer instead of a designated initializer ensures that everything is consistent because it must call a designated initializer that, in turn, is required to set everything up properly. Basically, you want to funnel all of your initialization through as few designated initializers as possible.
Just as with initializers, subclasses can override methods and computed properties. However, you have to be more careful with these. The compiler has fewer protections.
Even though it is possible, there is no requirement that an overriding method calls its superclass implementation. For example, let's add clean methods to our Building
and House
classes:
This is a great example of the need to override methods. We can provide common functionality in a superclass that can be extended in each of its subclasses instead of rewriting the same functionality in multiple classes.
We have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
discuss designated initializers, we first have to talk about convenience initializers. The normal initializer that we started with is really called a designated initializer. This means that they are core ways to initialize the class. You can also create convenience initializers which, as the name suggests, are there for convenience and are not a core way to initialize the class.
All convenience initializers must call a designated initializer and they do not have the ability to manually initialize properties like a designated initializer does. For example, we can define a convenience initializer on our Building
class that takes another building and makes a copy:
Now, as a convenience, you can create a new building using the properties from an existing building. The other rule about convenience initializers is that they cannot be used by a subclass. If you try to do that, you will get an error like this:
This is one of the main reasons that convenience initializers exist. Ideally, every class should only have one designated initializer. The fewer designated initializers you have, the easier it is to maintain your class hierarchy. This is because you will often add additional properties and other things that need to be initialized. Every time you add something like that, you will have to make sure that every designated initializer sets things up properly and consistently. Using a convenience initializer instead of a designated initializer ensures that everything is consistent because it must call a designated initializer that, in turn, is required to set everything up properly. Basically, you want to funnel all of your initialization through as few designated initializers as possible.
Just as with initializers, subclasses can override methods and computed properties. However, you have to be more careful with these. The compiler has fewer protections.
Even though it is possible, there is no requirement that an overriding method calls its superclass implementation. For example, let's add clean methods to our Building
and House
classes:
This is a great example of the need to override methods. We can provide common functionality in a superclass that can be extended in each of its subclasses instead of rewriting the same functionality in multiple classes.
We have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
Even though it is possible, there is no requirement that an overriding method calls its superclass implementation. For example, let's add clean methods to our Building
and House
classes:
This is a great example of the need to override methods. We can provide common functionality in a superclass that can be extended in each of its subclasses instead of rewriting the same functionality in multiple classes.
We have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
This is a great example of the need to override methods. We can provide common functionality in a superclass that can be extended in each of its subclasses instead of rewriting the same functionality in multiple classes.
We have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
We have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
have already talked about how classes are great for sharing functionality between a hierarchy of types. Another thing that makes classes powerful is that they allow code to interact with multiple types in a more general way. Any subclass can be used in code that treats it as if it were its superclass. For example, we might want to write a function that calculates the total square footage of an array of buildings. For this function, we don't care what specific type of building it is, we just need to have access to the squareFootage
property that is defined in the superclass. We can define our function to take an array of buildings and the actual array can contain House
instances:
This provides us with an even more powerful abstraction tool than the one we had when using structures. For example, let's consider a hypothetical class hierarchy of images. We might have a base class called Image
with subclasses for the different types of encodings like JPGImage
and PNGImage
. It is great to have the subclasses so that we can cleanly support multiple types of images but, once the image is loaded, we no longer need to be concerned with the type of encoding the image is saved in. Every other class that wants to manipulate or display the image can do so with a well-defined image superclass; the encoding of the image has been abstracted away from the rest of the code. Not only does this create easier to understand code but it also makes maintenance much easier. If we need to add another image encoding like
GIF, we can create another subclass and all the existing manipulation and display code can get GIF support with no changes to that code.
There are actually two different types of casting. So far, we have only seen the type of casting called upcasting. Predictably, the other type of casting is called downcasting.
What we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
we have seen so far is called upcasting because we are going up the class tree that we visualized earlier by treating a subclass as its superclass. Previously, we upcasted by assigning a subclass instance to a variable that was defined as its superclass. We could do the same thing using the as
operator instead, like this:
It is really personal preference as to which you should use.
Downcasting means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
means that we treat a superclass as one of its subclasses.
While upcasting can be done implicitly by using it in a function declared to use its superclass or by assigning it to a variable with its superclass type, downcasting must be done explicitly. This is because upcasting cannot fail based on the nature of its inheritance, but downcasting can. You can always treat a subclass as its superclass but you cannot guarantee that a superclass is, in fact, one of its specific subclasses. You can only downcast an instance that is, in fact, an instance of that class or one of its subclasses.
So far, we have covered two of the three types of classification in Swift: structure and class. The third classification is called enumeration. Enumerations are used to define a group of related values for an instance. For example, if we want values to represent one of the three primary colors, an enumeration is a great tool.
An enumeration is made up of cases much like a switch and uses the keyword enum
instead of struct
or class
. An enumeration for primary colors should look like this:
You can then define a variable with this type and assign it one of the cases:
Enumeration instances can be tested for a specific value as with any other type, using the equality operator (==
):
This method of comparison is familiar and useful for one or two possible values. However, there is a better way to test an enumeration for different values. Instead of using an if
statement, you can use a switch. This is a logical solution considering that enumerations are made up of cases and switches test for cases:
Enumerations are great because they provide the ability to store information that is not based on the basic types provided by Swift such as strings, integers, and doubles. There are many abstract concepts like our color example, that are not at all related to a basic type. However, you often want each enumeration case to have a raw value that is another type. For example, if we wanted to represent all of the coins in United States currency along with their monetary value, we could make our enumeration have an integer raw value type, like this:
The raw value type is specified in the same way that inheritance is specified with classes and then each case is individually assigned a specific value of that type.
You can access the raw value of a case at any time by using the rawValue
property:
Raw values are great for when every case in your enumeration has the same type of value associated with it and its value never changes. However, there are also scenarios where each case has different values associated with it and those values are different for each instance of the enumeration. You may even want a case that has multiple values associated with it. To do this, we use a feature of enumerations called associated values.
In the imperial case, the preceding code assigned feet
to a temporary constant and inches
to a temporary variable. The names match the labels used for the associated values but that is not necessary. The metric case shows that, if you want all of the temporary values to be constant, you can declare let
before the enumeration case. No matter how many associated values there are, let
only has to be written once instead of once for every value. The other case is the same as the metric case except that it creates a temporary variable instead of a constant.
This shows you that, with enumerations, switches have even more power than we saw previously.
Now that you understand how to use associated values, you might have noticed that they can change the conceptual nature of enumerations. Without associated values, an enumeration represents a list of abstract and constant possible values. An enumeration with associated values is different because two instances with the same case are not necessarily equal; each case could have different associated values. This means that the conceptual nature of enumerations is really a list of ways to look at a certain type of information. This is not a concrete rule but it is common and it gives you a better idea of the different types of information that can best be represented by enumerations. It will also help you make your own enumerations more understandable. Each case could theoretically represent a completely unrelated concept from the rest of the cases using associated values but that should be a sign that an enumeration may not be the best tool for that particular job.
Enumerations are actually very similar to structures. As with structures, enumerations can have methods and properties. To improve the Height
enumeration, we could add methods to access the height in any measurement system we wanted. As an example, let's implement a meters
method, as follows:
You now have a great overview of all of the different ways in which we can organize Swift code in a single file to make the code more understandable and maintainable. It is now time to discuss how we can separate our code into multiple files to improve it even more.
Enumeration instances can be tested for a specific value as with any other type, using the equality operator (==
):
This method of comparison is familiar and useful for one or two possible values. However, there is a better way to test an enumeration for different values. Instead of using an if
statement, you can use a switch. This is a logical solution considering that enumerations are made up of cases and switches test for cases:
Enumerations are great because they provide the ability to store information that is not based on the basic types provided by Swift such as strings, integers, and doubles. There are many abstract concepts like our color example, that are not at all related to a basic type. However, you often want each enumeration case to have a raw value that is another type. For example, if we wanted to represent all of the coins in United States currency along with their monetary value, we could make our enumeration have an integer raw value type, like this:
The raw value type is specified in the same way that inheritance is specified with classes and then each case is individually assigned a specific value of that type.
You can access the raw value of a case at any time by using the rawValue
property:
Raw values are great for when every case in your enumeration has the same type of value associated with it and its value never changes. However, there are also scenarios where each case has different values associated with it and those values are different for each instance of the enumeration. You may even want a case that has multiple values associated with it. To do this, we use a feature of enumerations called associated values.
In the imperial case, the preceding code assigned feet
to a temporary constant and inches
to a temporary variable. The names match the labels used for the associated values but that is not necessary. The metric case shows that, if you want all of the temporary values to be constant, you can declare let
before the enumeration case. No matter how many associated values there are, let
only has to be written once instead of once for every value. The other case is the same as the metric case except that it creates a temporary variable instead of a constant.
This shows you that, with enumerations, switches have even more power than we saw previously.
Now that you understand how to use associated values, you might have noticed that they can change the conceptual nature of enumerations. Without associated values, an enumeration represents a list of abstract and constant possible values. An enumeration with associated values is different because two instances with the same case are not necessarily equal; each case could have different associated values. This means that the conceptual nature of enumerations is really a list of ways to look at a certain type of information. This is not a concrete rule but it is common and it gives you a better idea of the different types of information that can best be represented by enumerations. It will also help you make your own enumerations more understandable. Each case could theoretically represent a completely unrelated concept from the rest of the cases using associated values but that should be a sign that an enumeration may not be the best tool for that particular job.
Enumerations are actually very similar to structures. As with structures, enumerations can have methods and properties. To improve the Height
enumeration, we could add methods to access the height in any measurement system we wanted. As an example, let's implement a meters
method, as follows:
You now have a great overview of all of the different ways in which we can organize Swift code in a single file to make the code more understandable and maintainable. It is now time to discuss how we can separate our code into multiple files to improve it even more.
This method of comparison is familiar and useful for one or two possible values. However, there is a better way to test an enumeration for different values. Instead of using an if
statement, you can use a switch. This is a logical solution considering that enumerations are made up of cases and switches test for cases:
Enumerations are great because they provide the ability to store information that is not based on the basic types provided by Swift such as strings, integers, and doubles. There are many abstract concepts like our color example, that are not at all related to a basic type. However, you often want each enumeration case to have a raw value that is another type. For example, if we wanted to represent all of the coins in United States currency along with their monetary value, we could make our enumeration have an integer raw value type, like this:
The raw value type is specified in the same way that inheritance is specified with classes and then each case is individually assigned a specific value of that type.
You can access the raw value of a case at any time by using the rawValue
property:
Raw values are great for when every case in your enumeration has the same type of value associated with it and its value never changes. However, there are also scenarios where each case has different values associated with it and those values are different for each instance of the enumeration. You may even want a case that has multiple values associated with it. To do this, we use a feature of enumerations called associated values.
In the imperial case, the preceding code assigned feet
to a temporary constant and inches
to a temporary variable. The names match the labels used for the associated values but that is not necessary. The metric case shows that, if you want all of the temporary values to be constant, you can declare let
before the enumeration case. No matter how many associated values there are, let
only has to be written once instead of once for every value. The other case is the same as the metric case except that it creates a temporary variable instead of a constant.
This shows you that, with enumerations, switches have even more power than we saw previously.
Now that you understand how to use associated values, you might have noticed that they can change the conceptual nature of enumerations. Without associated values, an enumeration represents a list of abstract and constant possible values. An enumeration with associated values is different because two instances with the same case are not necessarily equal; each case could have different associated values. This means that the conceptual nature of enumerations is really a list of ways to look at a certain type of information. This is not a concrete rule but it is common and it gives you a better idea of the different types of information that can best be represented by enumerations. It will also help you make your own enumerations more understandable. Each case could theoretically represent a completely unrelated concept from the rest of the cases using associated values but that should be a sign that an enumeration may not be the best tool for that particular job.
Enumerations are actually very similar to structures. As with structures, enumerations can have methods and properties. To improve the Height
enumeration, we could add methods to access the height in any measurement system we wanted. As an example, let's implement a meters
method, as follows:
You now have a great overview of all of the different ways in which we can organize Swift code in a single file to make the code more understandable and maintainable. It is now time to discuss how we can separate our code into multiple files to improve it even more.
are great because they provide the ability to store information that is not based on the basic types provided by Swift such as strings, integers, and doubles. There are many abstract concepts like our color example, that are not at all related to a basic type. However, you often want each enumeration case to have a raw value that is another type. For example, if we wanted to represent all of the coins in United States currency along with their monetary value, we could make our enumeration have an integer raw value type, like this:
The raw value type is specified in the same way that inheritance is specified with classes and then each case is individually assigned a specific value of that type.
You can access the raw value of a case at any time by using the rawValue
property:
Raw values are great for when every case in your enumeration has the same type of value associated with it and its value never changes. However, there are also scenarios where each case has different values associated with it and those values are different for each instance of the enumeration. You may even want a case that has multiple values associated with it. To do this, we use a feature of enumerations called associated values.
In the imperial case, the preceding code assigned feet
to a temporary constant and inches
to a temporary variable. The names match the labels used for the associated values but that is not necessary. The metric case shows that, if you want all of the temporary values to be constant, you can declare let
before the enumeration case. No matter how many associated values there are, let
only has to be written once instead of once for every value. The other case is the same as the metric case except that it creates a temporary variable instead of a constant.
This shows you that, with enumerations, switches have even more power than we saw previously.
Now that you understand how to use associated values, you might have noticed that they can change the conceptual nature of enumerations. Without associated values, an enumeration represents a list of abstract and constant possible values. An enumeration with associated values is different because two instances with the same case are not necessarily equal; each case could have different associated values. This means that the conceptual nature of enumerations is really a list of ways to look at a certain type of information. This is not a concrete rule but it is common and it gives you a better idea of the different types of information that can best be represented by enumerations. It will also help you make your own enumerations more understandable. Each case could theoretically represent a completely unrelated concept from the rest of the cases using associated values but that should be a sign that an enumeration may not be the best tool for that particular job.
Enumerations are actually very similar to structures. As with structures, enumerations can have methods and properties. To improve the Height
enumeration, we could add methods to access the height in any measurement system we wanted. As an example, let's implement a meters
method, as follows:
You now have a great overview of all of the different ways in which we can organize Swift code in a single file to make the code more understandable and maintainable. It is now time to discuss how we can separate our code into multiple files to improve it even more.
are great for when every case in your enumeration has the same type of value associated with it and its value never changes. However, there are also scenarios where each case has different values associated with it and those values are different for each instance of the enumeration. You may even want a case that has multiple values associated with it. To do this, we use a feature of enumerations called associated values.
In the imperial case, the preceding code assigned feet
to a temporary constant and inches
to a temporary variable. The names match the labels used for the associated values but that is not necessary. The metric case shows that, if you want all of the temporary values to be constant, you can declare let
before the enumeration case. No matter how many associated values there are, let
only has to be written once instead of once for every value. The other case is the same as the metric case except that it creates a temporary variable instead of a constant.
This shows you that, with enumerations, switches have even more power than we saw previously.
Now that you understand how to use associated values, you might have noticed that they can change the conceptual nature of enumerations. Without associated values, an enumeration represents a list of abstract and constant possible values. An enumeration with associated values is different because two instances with the same case are not necessarily equal; each case could have different associated values. This means that the conceptual nature of enumerations is really a list of ways to look at a certain type of information. This is not a concrete rule but it is common and it gives you a better idea of the different types of information that can best be represented by enumerations. It will also help you make your own enumerations more understandable. Each case could theoretically represent a completely unrelated concept from the rest of the cases using associated values but that should be a sign that an enumeration may not be the best tool for that particular job.
Enumerations are actually very similar to structures. As with structures, enumerations can have methods and properties. To improve the Height
enumeration, we could add methods to access the height in any measurement system we wanted. As an example, let's implement a meters
method, as follows:
You now have a great overview of all of the different ways in which we can organize Swift code in a single file to make the code more understandable and maintainable. It is now time to discuss how we can separate our code into multiple files to improve it even more.
If we want to move away from developing with a single file, we need to move away from playgrounds and create our first project. In order to simplify the project, we are going to create a command-line tool. This is a program without a graphical interface. As an exercise, we will redevelop our example program from Chapter 2, Building Blocks – Variables, Collections, and Flow Control which managed invitees to a party. We will develop an app with a graphical interface in Chapter 11, A Whole New World – Developing an App.
To create a new command-line tool project, open Xcode and from the menu bar on the top, select File | New | Project…. A window will appear allowing you to select a template for the project. You should choose Command Line Tool from the OS X | Application menu:
From there, click Next and then give the project a name like Learning Swift Command Line
. Any Organization Name and Identifier are fine. Finally, make sure that Swift is selected from the Language dropdown and click Next again. Now, save the project somewhere that you can find later and click Create.
This should feel pretty similar to a playground except that we can no longer see the output of the code on the right. In a regular project like this, the code is not run automatically for you. The code will still be analyzed for errors as you write it, but you must run it yourself whenever you want to test it. To run the code, you can click the run button on the toolbar, which looks like a play button.
Now that we have successfully created our command-line project, let's create our first new file. It is common to create a separate file for each type that you create. Let's start by creating a file for an invitee
class. We want to add the file to the same file group as the main.swift
file, so click on that group. You can then click on the plus sign (+) in the lower left of the window and select New File. From that window, select OS X | Source | Swift File and click Next:
The new file will be placed in whatever folder was selected before entering the dialog. You can always drag a file around to organize it however you want. A great place for this file is next to main.swift
. Name your new file Invitee.swift
and click Create. Let's add a simple Invitee
structure to this file. We want Invitee
to have a name and to be able to ask them to the party with or without a show:
An important principle in code design is called separation of concerns. The idea is that every file and every type should have a clear and well-defined concern. You should avoid having two files or types responsible for the same thing and you want it to be clear why each file and type exists.
Now that we have our basic data structures, we can use a smarter container for our list of invitees. This list contains the logic for assigning a random invitee a genre. Let's start by defining the structure with some properties:
This makes our other methods cleaner and easier to understand. The first thing we want to allow is the inviting of a random invitee and then asking them to bring a show from a specific genre
:
Lastly, we want to be able to invite everyone else to just bring themselves:
We now have all of our custom types and we can return to the main.swift
file to finish the logic of the program. To switch back, you can just click on the file again in Project Navigator (the list of files on the left). Here, all we want to do is to create our invitee list and a list of genres with example shows. Then, we can loop through our genres and ask our invitee list to do the inviting:
As your project gets larger, it can be cumbersome to have just one single list of files. It helps to organize your files into folders to help differentiate which role they are playing in your app. In Project Navigator, folders are called groups. You can create a new group by selecting the group you would like to add the new group to, and going to File | New | Group. It isn't terribly important exactly how you group your files; the important thing is that you should be able to come up with a relatively simple system that makes sense. If you are having trouble doing that, you should consider how you could improve the way you are breaking up your code. If you are having trouble categorizing your files, then your code is probably not being broken up in a maintainable way.
I would recommend using lots of files and groups to better separate your code. However, the drawback of that is that Project Navigator can fill up pretty quickly and become hard to navigate around. A great trick in Xcode to navigate to files more quickly is to use the keyboard shortcut Command + Shift + O. This displays the Open Quickly search. Here, you can start to type the name of the file you want to open and Xcode shows you all of the matching files. Use the arrow keys to navigate up and down and press Enter to open the file you want.
From there, click Next and then give the project a name like Learning Swift Command Line
. Any Organization Name and Identifier are fine. Finally, make sure that Swift is selected from the Language dropdown and click Next again. Now, save the project somewhere that you can find later and click Create.
This should feel pretty similar to a playground except that we can no longer see the output of the code on the right. In a regular project like this, the code is not run automatically for you. The code will still be analyzed for errors as you write it, but you must run it yourself whenever you want to test it. To run the code, you can click the run button on the toolbar, which looks like a play button.
Now that we have successfully created our command-line project, let's create our first new file. It is common to create a separate file for each type that you create. Let's start by creating a file for an invitee
class. We want to add the file to the same file group as the main.swift
file, so click on that group. You can then click on the plus sign (+) in the lower left of the window and select New File. From that window, select OS X | Source | Swift File and click Next:
The new file will be placed in whatever folder was selected before entering the dialog. You can always drag a file around to organize it however you want. A great place for this file is next to main.swift
. Name your new file Invitee.swift
and click Create. Let's add a simple Invitee
structure to this file. We want Invitee
to have a name and to be able to ask them to the party with or without a show:
An important principle in code design is called separation of concerns. The idea is that every file and every type should have a clear and well-defined concern. You should avoid having two files or types responsible for the same thing and you want it to be clear why each file and type exists.
Now that we have our basic data structures, we can use a smarter container for our list of invitees. This list contains the logic for assigning a random invitee a genre. Let's start by defining the structure with some properties:
This makes our other methods cleaner and easier to understand. The first thing we want to allow is the inviting of a random invitee and then asking them to bring a show from a specific genre
:
Lastly, we want to be able to invite everyone else to just bring themselves:
We now have all of our custom types and we can return to the main.swift
file to finish the logic of the program. To switch back, you can just click on the file again in Project Navigator (the list of files on the left). Here, all we want to do is to create our invitee list and a list of genres with example shows. Then, we can loop through our genres and ask our invitee list to do the inviting:
As your project gets larger, it can be cumbersome to have just one single list of files. It helps to organize your files into folders to help differentiate which role they are playing in your app. In Project Navigator, folders are called groups. You can create a new group by selecting the group you would like to add the new group to, and going to File | New | Group. It isn't terribly important exactly how you group your files; the important thing is that you should be able to come up with a relatively simple system that makes sense. If you are having trouble doing that, you should consider how you could improve the way you are breaking up your code. If you are having trouble categorizing your files, then your code is probably not being broken up in a maintainable way.
I would recommend using lots of files and groups to better separate your code. However, the drawback of that is that Project Navigator can fill up pretty quickly and become hard to navigate around. A great trick in Xcode to navigate to files more quickly is to use the keyboard shortcut Command + Shift + O. This displays the Open Quickly search. Here, you can start to type the name of the file you want to open and Xcode shows you all of the matching files. Use the arrow keys to navigate up and down and press Enter to open the file you want.
successfully created our command-line project, let's create our first new file. It is common to create a separate file for each type that you create. Let's start by creating a file for an invitee
class. We want to add the file to the same file group as the main.swift
file, so click on that group. You can then click on the plus sign (+) in the lower left of the window and select New File. From that window, select OS X | Source | Swift File and click Next:
The new file will be placed in whatever folder was selected before entering the dialog. You can always drag a file around to organize it however you want. A great place for this file is next to main.swift
. Name your new file Invitee.swift
and click Create. Let's add a simple Invitee
structure to this file. We want Invitee
to have a name and to be able to ask them to the party with or without a show:
An important principle in code design is called separation of concerns. The idea is that every file and every type should have a clear and well-defined concern. You should avoid having two files or types responsible for the same thing and you want it to be clear why each file and type exists.
Now that we have our basic data structures, we can use a smarter container for our list of invitees. This list contains the logic for assigning a random invitee a genre. Let's start by defining the structure with some properties:
This makes our other methods cleaner and easier to understand. The first thing we want to allow is the inviting of a random invitee and then asking them to bring a show from a specific genre
:
Lastly, we want to be able to invite everyone else to just bring themselves:
We now have all of our custom types and we can return to the main.swift
file to finish the logic of the program. To switch back, you can just click on the file again in Project Navigator (the list of files on the left). Here, all we want to do is to create our invitee list and a list of genres with example shows. Then, we can loop through our genres and ask our invitee list to do the inviting:
As your project gets larger, it can be cumbersome to have just one single list of files. It helps to organize your files into folders to help differentiate which role they are playing in your app. In Project Navigator, folders are called groups. You can create a new group by selecting the group you would like to add the new group to, and going to File | New | Group. It isn't terribly important exactly how you group your files; the important thing is that you should be able to come up with a relatively simple system that makes sense. If you are having trouble doing that, you should consider how you could improve the way you are breaking up your code. If you are having trouble categorizing your files, then your code is probably not being broken up in a maintainable way.
I would recommend using lots of files and groups to better separate your code. However, the drawback of that is that Project Navigator can fill up pretty quickly and become hard to navigate around. A great trick in Xcode to navigate to files more quickly is to use the keyboard shortcut Command + Shift + O. This displays the Open Quickly search. Here, you can start to type the name of the file you want to open and Xcode shows you all of the matching files. Use the arrow keys to navigate up and down and press Enter to open the file you want.
This makes our other methods cleaner and easier to understand. The first thing we want to allow is the inviting of a random invitee and then asking them to bring a show from a specific genre
:
Lastly, we want to be able to invite everyone else to just bring themselves:
We now have all of our custom types and we can return to the main.swift
file to finish the logic of the program. To switch back, you can just click on the file again in Project Navigator (the list of files on the left). Here, all we want to do is to create our invitee list and a list of genres with example shows. Then, we can loop through our genres and ask our invitee list to do the inviting:
As your project gets larger, it can be cumbersome to have just one single list of files. It helps to organize your files into folders to help differentiate which role they are playing in your app. In Project Navigator, folders are called groups. You can create a new group by selecting the group you would like to add the new group to, and going to File | New | Group. It isn't terribly important exactly how you group your files; the important thing is that you should be able to come up with a relatively simple system that makes sense. If you are having trouble doing that, you should consider how you could improve the way you are breaking up your code. If you are having trouble categorizing your files, then your code is probably not being broken up in a maintainable way.
I would recommend using lots of files and groups to better separate your code. However, the drawback of that is that Project Navigator can fill up pretty quickly and become hard to navigate around. A great trick in Xcode to navigate to files more quickly is to use the keyboard shortcut Command + Shift + O. This displays the Open Quickly search. Here, you can start to type the name of the file you want to open and Xcode shows you all of the matching files. Use the arrow keys to navigate up and down and press Enter to open the file you want.
gets larger, it can be cumbersome to have just one single list of files. It helps to organize your files into folders to help differentiate which role they are playing in your app. In Project Navigator, folders are called groups. You can create a new group by selecting the group you would like to add the new group to, and going to File | New | Group. It isn't terribly important exactly how you group your files; the important thing is that you should be able to come up with a relatively simple system that makes sense. If you are having trouble doing that, you should consider how you could improve the way you are breaking up your code. If you are having trouble categorizing your files, then your code is probably not being broken up in a maintainable way.
I would recommend using lots of files and groups to better separate your code. However, the drawback of that is that Project Navigator can fill up pretty quickly and become hard to navigate around. A great trick in Xcode to navigate to files more quickly is to use the keyboard shortcut Command + Shift + O. This displays the Open Quickly search. Here, you can start to type the name of the file you want to open and Xcode shows you all of the matching files. Use the arrow keys to navigate up and down and press Enter to open the file you want.
Up until this point, we had to define our entire custom type in a single file. However, it is sometimes useful to separate out part of our custom types into different files, or even just in the same file. To achieve this, Swift provides a feature called extensions. Extensions allow us to add additional functionality to existing types from anywhere.
This functionality is limited to additional functions and additional computed properties:
This is just one simple idea, but it is often incredibly useful to extend the built-in types.
Now that we have a good overview of what tools we have at our disposal for organizing our code, it is time to discuss an important concept in programming called scope.
Scope is all about which code has access to which other pieces of code. Swift makes it relatively easy to understand because all scope is defined by curly brackets ({}
). Essentially, code in curly brackets can only access other code in the same curly brackets.
To illustrate scope, let's look at some simple code:
As you can see, outer
can be accessed from both in and out of the if
statement. However, since inner
was defined in the curly brackets of the if
statement, it cannot be accessed from outside of them. This is true of structs, classes, loops, functions, and any other structure that involves curly brackets. Everything that is not in curly brackets is considered to be at
global scope, meaning that anything can access it.
Sometimes, it is useful to control scope yourself. To do this, you can define types within other types:
This can be useful to better segment your code but it is also great for hiding code that is not useful to any code outside other code. As you program in bigger projects, you will start to rely on Xcode's autocomplete feature more and more. In big code bases, autocomplete offers a lot of options, and nesting types into other types is a great way to reduce unnecessary clutter in the autocomplete list.
scope, let's look at some simple code:
As you can see, outer
can be accessed from both in and out of the if
statement. However, since inner
was defined in the curly brackets of the if
statement, it cannot be accessed from outside of them. This is true of structs, classes, loops, functions, and any other structure that involves curly brackets. Everything that is not in curly brackets is considered to be at
global scope, meaning that anything can access it.
Sometimes, it is useful to control scope yourself. To do this, you can define types within other types:
This can be useful to better segment your code but it is also great for hiding code that is not useful to any code outside other code. As you program in bigger projects, you will start to rely on Xcode's autocomplete feature more and more. In big code bases, autocomplete offers a lot of options, and nesting types into other types is a great way to reduce unnecessary clutter in the autocomplete list.
useful to control scope yourself. To do this, you can define types within other types:
This can be useful to better segment your code but it is also great for hiding code that is not useful to any code outside other code. As you program in bigger projects, you will start to rely on Xcode's autocomplete feature more and more. In big code bases, autocomplete offers a lot of options, and nesting types into other types is a great way to reduce unnecessary clutter in the autocomplete list.
Swift provides another set of tools that helps to control what code other code has access to called access controls. All code is actually given three levels of access control:
This is a fantastic way of improving the idea of abstractions. The simpler the outside view of your code, the easier it is to understand and use your abstraction. You should look at every file and every type as a small abstraction. In any abstraction, you want the outside world to have as little knowledge of the inner workings of it as possible. You should always keep in mind how you want your abstraction to be used and hide any code that does not serve that purpose. This is because code becomes harder and harder to understand and maintain as the walls between different parts of the code break down. You will end up with code that resembles a bowl of pasta. In the same way that it can be difficult to find where one noodle starts and ends, code with lots of interdependencies and minimal barriers between code components is very hard to make sense of. An abstraction that provides too much knowledge or access about its internal workings is often called a leaky abstraction.
Public code is defined in the same way, except that you would use the public
keyword instead of private
. However, since we will not study designing your own modules, this is not useful to us. It is good to know it exists for future learning but the default
internal access level is enough for our apps.