As we learned in Chapter 2, Building Blocks – Variables, Collections, and Flow Control, Swift is a strongly typed language, which means that every piece of data must have a type. Not only can we take advantage of this to reduce the clutter in our code, we can also leverage it to let the compiler catch bugs for us. The earlier we catch a bug, the better. Besides not writing them in the first place, the earliest place where we can catch a bug is when the compiler reports an error.
Two big tools that Swift provides to achieve this are called protocols and generics. Both of them use the type system to make our intentions clear to the compiler so that it can catch more bugs for us.
In this chapter, we will cover the following topics:
The first tool we will look at is protocols. A protocol is essentially a contract that a type can sign, specifying that it will provide a certain interface to other components. This relationship is significantly looser than the relationship a subclass has with its superclass. A protocol does not provide any implementation to the types that implement them. Instead, a type can implement them in any way that they like.
Let's take a look at how we define a protocol, in order to understand them better.
Let's say we have some code that needs to interact with a collection of strings. We don't actually care what order they are stored in and we only need to be able to add and enumerate elements inside the container. One option would be to simply use an array, but an array does way more than we need it to. What if we decide later that we would rather write and read the elements from the file system? Furthermore, what if we want to write a container that would intelligently start using the file system as it got really large? We can make our code flexible enough to do this in the future by defining a string container protocol, which is a loose contract that defines what we need it to do. This protocol might look similar to the following code:
A type "signs the contract" of a protocol in the same way that a class inherits from another class except that structures and enumerations can also implement protocols:
The count
property will always just return the number of elements in our strings
array. The addString:
method can simply add the string to our array. Finally, our enumerateString:
method just needs to loop through our array and call the handler with each element.
With this implementation we are doing something slightly strange with the dictionary. We defined it to have no values; it is simply a collection of keys. This allows us to store our strings without any regard to the order they are in.
Protocols can be made more flexible using a feature called type aliases. They act as a placeholder for a type that will be defined later when the protocol is being implemented. For example, instead of creating an interface that specifically includes strings, we can create an interface for a container that can hold any type of value, as shown:
Now, we can create another string bag that uses the new Container
protocol with a type alias instead of the StringContainer
protocol. To do this, we not only need to implement each of the methods, we also need to give a definition for the type alias, as shown:
The only difference between these two pieces of code is that the type alias has been defined to be an integer in the second case instead of a string. We could use copy and paste to create a container of virtually any type, but as usual, doing a lot of copy and paste is a sign that there is a better solution. Also, you may notice that our new Container
protocol isn't actually that useful on its own because with our existing techniques, we can't treat a variable as just a Container
. If we are going to interact with an instance that implements this protocol, we need to know what type it has assigned the type alias to.
Swift provides a tool called generics to solve both of these problems.
A generic is very similar to a type alias. The difference is that the exact type of a generic is determined by the context in which it is being used, instead of being determined by the implementing types. This also means that a generic only has a single implementation that must support all possible types. Let's start by defining a generic function.
In Chapter 5, A Modern Paradigm – Closures and Functional Programming, we created a function that helped us find the first number in an array of numbers that passes a test:
This would be great if we only ever dealt with arrays of integers
, but clearly it would be helpful to be able to do this with other types. In fact, dare I say, all types? We achieve this very simply by making our function generic. A generic function is declared similar to a normal function, but you include a list of comma-separated placeholders inside angled brackets (<>
) at the end of the function name, as shown:
We use this function similarly to any other function, as shown:
So what happens if the type we use in our closure doesn't match the type of array we pass in?
As you can see, we get an error that the types don't match.
You may have noticed that we have actually used generic functions before. All of the built in functions we looked at in Chapter 5, A Modern Paradigm – Closures and Functional Programming, such as map
and filter
are generic; they can be used with any type.
We have even experienced generic types before. Arrays and dictionaries are also generic. The Swift team didn't have to write a new implementation of array and dictionary for every type that we might want to use inside the containers; they created them as generic types.
Similar to a generic function, a generic type is defined just like a normal type but it has a list of placeholders at the end of its name. Earlier in this chapter, we created our own containers for strings and integers
. Let's make a generic version of these containers, as shown:
One interesting case to consider is if we try to initialize a bag with an empty array:
This is great because not only can the compiler determine the generic placeholder types based on the variables we pass to them, it can also determine the type based on how we are using the result.
Before we jump right into solving the problem, let's take a look at a simpler form of type constraints.
Let's say that we want to write a function that can determine the index of an instance within an array using an equality check. Our first attempt will probably look similar to the following code:
A type constraint looks similar to a type implementing a protocol using a colon (:
) after a placeholder name. Now, the compiler is satisfied that every possible type can be compared using the equality operator. If we were to try to call this function with a type that is not equatable, the compiler would produce an error:
This is another case where the compiler can save us from ourselves.
Now the compiler is happy and we can start to use our Bag2
struct with any type that is Hashable
. We are close to solving our Container
problem, but we need a constraint on the type alias of Container
, not Container
itself. To do that, we can use a where
clause.
You can specify any number of where
clauses you want after you have defined each placeholder type. They allow you to represent more complicated relationships. If we want to write a function that can check if our container contains a particular value, we can require that the element type is equatable:
Chapter 5, A Modern Paradigm – Closures and Functional Programming, we created a function that helped us find the first number in an array of numbers that passes a test:
This would be great if we only ever dealt with arrays of integers
, but clearly it would be helpful to be able to do this with other types. In fact, dare I say, all types? We achieve this very simply by making our function generic. A generic function is declared similar to a normal function, but you include a list of comma-separated placeholders inside angled brackets (<>
) at the end of the function name, as shown:
We use this function similarly to any other function, as shown:
So what happens if the type we use in our closure doesn't match the type of array we pass in?
As you can see, we get an error that the types don't match.
You may have noticed that we have actually used generic functions before. All of the built in functions we looked at in Chapter 5, A Modern Paradigm – Closures and Functional Programming, such as map
and filter
are generic; they can be used with any type.
We have even experienced generic types before. Arrays and dictionaries are also generic. The Swift team didn't have to write a new implementation of array and dictionary for every type that we might want to use inside the containers; they created them as generic types.
Similar to a generic function, a generic type is defined just like a normal type but it has a list of placeholders at the end of its name. Earlier in this chapter, we created our own containers for strings and integers
. Let's make a generic version of these containers, as shown:
One interesting case to consider is if we try to initialize a bag with an empty array:
This is great because not only can the compiler determine the generic placeholder types based on the variables we pass to them, it can also determine the type based on how we are using the result.
Before we jump right into solving the problem, let's take a look at a simpler form of type constraints.
Let's say that we want to write a function that can determine the index of an instance within an array using an equality check. Our first attempt will probably look similar to the following code:
A type constraint looks similar to a type implementing a protocol using a colon (:
) after a placeholder name. Now, the compiler is satisfied that every possible type can be compared using the equality operator. If we were to try to call this function with a type that is not equatable, the compiler would produce an error:
This is another case where the compiler can save us from ourselves.
Now the compiler is happy and we can start to use our Bag2
struct with any type that is Hashable
. We are close to solving our Container
problem, but we need a constraint on the type alias of Container
, not Container
itself. To do that, we can use a where
clause.
You can specify any number of where
clauses you want after you have defined each placeholder type. They allow you to represent more complicated relationships. If we want to write a function that can check if our container contains a particular value, we can require that the element type is equatable:
One interesting case to consider is if we try to initialize a bag with an empty array:
This is great because not only can the compiler determine the generic placeholder types based on the variables we pass to them, it can also determine the type based on how we are using the result.
Before we jump right into solving the problem, let's take a look at a simpler form of type constraints.
Let's say that we want to write a function that can determine the index of an instance within an array using an equality check. Our first attempt will probably look similar to the following code:
A type constraint looks similar to a type implementing a protocol using a colon (:
) after a placeholder name. Now, the compiler is satisfied that every possible type can be compared using the equality operator. If we were to try to call this function with a type that is not equatable, the compiler would produce an error:
This is another case where the compiler can save us from ourselves.
Now the compiler is happy and we can start to use our Bag2
struct with any type that is Hashable
. We are close to solving our Container
problem, but we need a constraint on the type alias of Container
, not Container
itself. To do that, we can use a where
clause.
You can specify any number of where
clauses you want after you have defined each placeholder type. They allow you to represent more complicated relationships. If we want to write a function that can check if our container contains a particular value, we can require that the element type is equatable:
we jump right into solving the problem, let's take a look at a simpler form of type constraints.
Let's say that we want to write a function that can determine the index of an instance within an array using an equality check. Our first attempt will probably look similar to the following code:
A type constraint looks similar to a type implementing a protocol using a colon (:
) after a placeholder name. Now, the compiler is satisfied that every possible type can be compared using the equality operator. If we were to try to call this function with a type that is not equatable, the compiler would produce an error:
This is another case where the compiler can save us from ourselves.
Now the compiler is happy and we can start to use our Bag2
struct with any type that is Hashable
. We are close to solving our Container
problem, but we need a constraint on the type alias of Container
, not Container
itself. To do that, we can use a where
clause.
You can specify any number of where
clauses you want after you have defined each placeholder type. They allow you to represent more complicated relationships. If we want to write a function that can check if our container contains a particular value, we can require that the element type is equatable:
A type constraint looks similar to a type implementing a protocol using a colon (:
) after a placeholder name. Now, the compiler is satisfied that every possible type can be compared using the equality operator. If we were to try to call this function with a type that is not equatable, the compiler would produce an error:
This is another case where the compiler can save us from ourselves.
Now the compiler is happy and we can start to use our Bag2
struct with any type that is Hashable
. We are close to solving our Container
problem, but we need a constraint on the type alias of Container
, not Container
itself. To do that, we can use a where
clause.
You can specify any number of where
clauses you want after you have defined each placeholder type. They allow you to represent more complicated relationships. If we want to write a function that can check if our container contains a particular value, we can require that the element type is equatable:
The two main generics that we will probably want to extend are arrays and dictionaries. These are the two most prominent containers provided by Swift and are used in virtually every app. Extending a generic type is simple once you understand that an extension itself does not need to be generic.
Knowing that an array is declared as struct Array<Element>
, your first instinct to extend an array might look something similar to this:
This is more dangerous because the compiler doesn't detect an error. This is wrong because you are actually declaring a new placeholder Element
to be used within the method. This new Element
has nothing to do with the Element
defined in Array
itself. For example, you might get a confusing error if you tried to compare a parameter to the method to an element of the Array:
As you can see, we continue to use the placeholder Element
within our extension. This allows us to call the passed in test closure for each element in the array. Now, what if we want to be able to add a method that will check if an element exists using the equality operator? The problem that we will run into is that array does not place a type constraint on Element
requiring it to be Equatable
. To do this, we can add an extra constraint to our extension.
We first discussed how we can extend existing types in Chapter 3, One Piece at a Time – Types, Scopes, and Projects. In Swift 2, Apple added the ability to extend protocols. This has some fascinating implications, but before we dive into those, let's take a look at an example of adding a method to the Comparable
protocol:
extension Comparable { func isBetween(a: Self, b: Self) -> Bool { return a < self && self < b } }
This is a really powerful tool. In fact, this is how the Swift team implemented many of the functional methods we saw in Chapter 5, A Modern Paradigm – Closures and Functional Programming. They did not have to implement the map method on arrays, dictionaries, or on any other sequence that should be mappable; instead, they implemented it directly on SequenceType
.
This shows that similarly, protocol extensions can be used for inheritance, and it can also be applied to both classes and structures and types can also inherit this functionality from multiple different protocols because there is no limit to the number of protocols a type can implement. However, there are two major differences between the two.
First, types cannot inherit stored properties from protocols, because extensions cannot define them. Protocols can define read only properties but every instance will have to redeclare them as properties:
protocol Building { var squareFootage: Int {get} } struct House: Building { let squareFootage: Int } struct Factory: Building { let squareFootage: Int }
Second, method overriding does not work in the same way with protocol extensions. With protocols, Swift does not intelligently figure out which version of a method to call based on the actual type of an instance. With class inheritance, Swift will call the version of a method that is most directly associated with the instance. Remember, when we called clean on an instance of our House subclass in Chapter 3, One Piece at a Time – Types, Scopes, and Projects, it calls the overriding version of clean, as shown:
class Building { // ... func clean() { print( "Scrub \(self.squareFootage) square feet of floors" ) } } class House: Building { // ... override func clean() { print("Make \(self.numberOfBedrooms) beds") print("Clean \(self.numberOfBathrooms) bathrooms") } } let building: Building = House( squareFootage: 800, numberOfBedrooms: 2, numberOfBathrooms: 1 ) building.clean() // Make 2 beds // Clean 1 bathroom
When we call clean on the house variable which is of type House
, it calls the house version; however, when we cast the variable to a Building
and then call it, it calls the building version.
This is more dangerous because the compiler doesn't detect an error. This is wrong because you are actually declaring a new placeholder Element
to be used within the method. This new Element
has nothing to do with the Element
defined in Array
itself. For example, you might get a confusing error if you tried to compare a parameter to the method to an element of the Array:
As you can see, we continue to use the placeholder Element
within our extension. This allows us to call the passed in test closure for each element in the array. Now, what if we want to be able to add a method that will check if an element exists using the equality operator? The problem that we will run into is that array does not place a type constraint on Element
requiring it to be Equatable
. To do this, we can add an extra constraint to our extension.
We first discussed how we can extend existing types in Chapter 3, One Piece at a Time – Types, Scopes, and Projects. In Swift 2, Apple added the ability to extend protocols. This has some fascinating implications, but before we dive into those, let's take a look at an example of adding a method to the Comparable
protocol:
extension Comparable { func isBetween(a: Self, b: Self) -> Bool { return a < self && self < b } }
This is a really powerful tool. In fact, this is how the Swift team implemented many of the functional methods we saw in Chapter 5, A Modern Paradigm – Closures and Functional Programming. They did not have to implement the map method on arrays, dictionaries, or on any other sequence that should be mappable; instead, they implemented it directly on SequenceType
.
This shows that similarly, protocol extensions can be used for inheritance, and it can also be applied to both classes and structures and types can also inherit this functionality from multiple different protocols because there is no limit to the number of protocols a type can implement. However, there are two major differences between the two.
First, types cannot inherit stored properties from protocols, because extensions cannot define them. Protocols can define read only properties but every instance will have to redeclare them as properties:
protocol Building { var squareFootage: Int {get} } struct House: Building { let squareFootage: Int } struct Factory: Building { let squareFootage: Int }
Second, method overriding does not work in the same way with protocol extensions. With protocols, Swift does not intelligently figure out which version of a method to call based on the actual type of an instance. With class inheritance, Swift will call the version of a method that is most directly associated with the instance. Remember, when we called clean on an instance of our House subclass in Chapter 3, One Piece at a Time – Types, Scopes, and Projects, it calls the overriding version of clean, as shown:
class Building { // ... func clean() { print( "Scrub \(self.squareFootage) square feet of floors" ) } } class House: Building { // ... override func clean() { print("Make \(self.numberOfBedrooms) beds") print("Clean \(self.numberOfBathrooms) bathrooms") } } let building: Building = House( squareFootage: 800, numberOfBedrooms: 2, numberOfBathrooms: 1 ) building.clean() // Make 2 beds // Clean 1 bathroom
When we call clean on the house variable which is of type House
, it calls the house version; however, when we cast the variable to a Building
and then call it, it calls the building version.
We first discussed how we can extend existing types in Chapter 3, One Piece at a Time – Types, Scopes, and Projects. In Swift 2, Apple added the ability to extend protocols. This has some fascinating implications, but before we dive into those, let's take a look at an example of adding a method to the Comparable
protocol:
extension Comparable { func isBetween(a: Self, b: Self) -> Bool { return a < self && self < b } }
This is a really powerful tool. In fact, this is how the Swift team implemented many of the functional methods we saw in Chapter 5, A Modern Paradigm – Closures and Functional Programming. They did not have to implement the map method on arrays, dictionaries, or on any other sequence that should be mappable; instead, they implemented it directly on SequenceType
.
This shows that similarly, protocol extensions can be used for inheritance, and it can also be applied to both classes and structures and types can also inherit this functionality from multiple different protocols because there is no limit to the number of protocols a type can implement. However, there are two major differences between the two.
First, types cannot inherit stored properties from protocols, because extensions cannot define them. Protocols can define read only properties but every instance will have to redeclare them as properties:
protocol Building { var squareFootage: Int {get} } struct House: Building { let squareFootage: Int } struct Factory: Building { let squareFootage: Int }
Second, method overriding does not work in the same way with protocol extensions. With protocols, Swift does not intelligently figure out which version of a method to call based on the actual type of an instance. With class inheritance, Swift will call the version of a method that is most directly associated with the instance. Remember, when we called clean on an instance of our House subclass in Chapter 3, One Piece at a Time – Types, Scopes, and Projects, it calls the overriding version of clean, as shown:
class Building { // ... func clean() { print( "Scrub \(self.squareFootage) square feet of floors" ) } } class House: Building { // ... override func clean() { print("Make \(self.numberOfBedrooms) beds") print("Clean \(self.numberOfBathrooms) bathrooms") } } let building: Building = House( squareFootage: 800, numberOfBedrooms: 2, numberOfBathrooms: 1 ) building.clean() // Make 2 beds // Clean 1 bathroom
When we call clean on the house variable which is of type House
, it calls the house version; however, when we cast the variable to a Building
and then call it, it calls the building version.
how we can extend existing types in Chapter 3, One Piece at a Time – Types, Scopes, and Projects. In Swift 2, Apple added the ability to extend protocols. This has some fascinating implications, but before we dive into those, let's take a look at an example of adding a method to the Comparable
protocol:
extension Comparable { func isBetween(a: Self, b: Self) -> Bool { return a < self && self < b } }
This is a really powerful tool. In fact, this is how the Swift team implemented many of the functional methods we saw in Chapter 5, A Modern Paradigm – Closures and Functional Programming. They did not have to implement the map method on arrays, dictionaries, or on any other sequence that should be mappable; instead, they implemented it directly on SequenceType
.
This shows that similarly, protocol extensions can be used for inheritance, and it can also be applied to both classes and structures and types can also inherit this functionality from multiple different protocols because there is no limit to the number of protocols a type can implement. However, there are two major differences between the two.
First, types cannot inherit stored properties from protocols, because extensions cannot define them. Protocols can define read only properties but every instance will have to redeclare them as properties:
protocol Building { var squareFootage: Int {get} } struct House: Building { let squareFootage: Int } struct Factory: Building { let squareFootage: Int }
Second, method overriding does not work in the same way with protocol extensions. With protocols, Swift does not intelligently figure out which version of a method to call based on the actual type of an instance. With class inheritance, Swift will call the version of a method that is most directly associated with the instance. Remember, when we called clean on an instance of our House subclass in Chapter 3, One Piece at a Time – Types, Scopes, and Projects, it calls the overriding version of clean, as shown:
class Building { // ... func clean() { print( "Scrub \(self.squareFootage) square feet of floors" ) } } class House: Building { // ... override func clean() { print("Make \(self.numberOfBedrooms) beds") print("Clean \(self.numberOfBathrooms) bathrooms") } } let building: Building = House( squareFootage: 800, numberOfBedrooms: 2, numberOfBathrooms: 1 ) building.clean() // Make 2 beds // Clean 1 bathroom
When we call clean on the house variable which is of type House
, it calls the house version; however, when we cast the variable to a Building
and then call it, it calls the building version.
One cool part of Swift is generators and sequences. They provide an easy way to iterate over a list of values. Ultimately, they boil down to two different protocols: GeneratorType
and SequenceType
. If you implement the SequenceType
protocol in your custom types, it allows you to use the for-in loop over an instance of your type. In this section, we will look at how we can do that.
The most critical part of this is the GeneratorType
protocol. Essentially, a generator is an object that you can repeatedly ask for the next object in a series until there are no objects left. Most of the time you can simply use an array for this, but it is not always the best solution. For example, you can even make a generator that is infinite.
The implementation looks similar to this:
We need to set up some sort of a condition so that the loop doesn't go on forever. In this case, we break out of the loop once the numbers get above 10. However, this code is pretty ugly, so Swift also defines the protocol called SequenceType
to clean it up.
SequenceType
is another protocol that is defined as having a type alias for a GeneratorType
and a method called generate
that returns a new generator of that type. We could declare a simple sequence for our FibonacciGenerator
, as follows:
In this version of FibonacciSequence
, we create a new generator every time generate is called that takes a closure that does the same thing that our original FibonacciGenerator
was doing. We declare the values
variable outside of the closure so that we can use it to store the state between calls to the closure. If your generator is simple and doesn't require a complicated state, using the AnyGenerator
generic is a great way to go.
Now let's use this FibonacciSequence
to solve the kind of math problem that computers are great at.
What if we want to know what is the result of multiplying every number in the Fibonacci sequence under 50? We can try to use a calculator and painstakingly enter in all of the numbers, but it is much more efficient to do it in Swift.
Notice that when we refer to the element type, we must go through the generator type.
We can even add an extension to SequenceType
with a method that will create a limiter for us:
Now, we can easily limit our Fibonacci sequence to only values under 50:
Almost instantaneously, our program will return the result 2,227,680. Now we can really understand why we call these devices computers.
The implementation looks similar to this:
We need to set up some sort of a condition so that the loop doesn't go on forever. In this case, we break out of the loop once the numbers get above 10. However, this code is pretty ugly, so Swift also defines the protocol called SequenceType
to clean it up.
SequenceType
is another protocol that is defined as having a type alias for a GeneratorType
and a method called generate
that returns a new generator of that type. We could declare a simple sequence for our FibonacciGenerator
, as follows:
In this version of FibonacciSequence
, we create a new generator every time generate is called that takes a closure that does the same thing that our original FibonacciGenerator
was doing. We declare the values
variable outside of the closure so that we can use it to store the state between calls to the closure. If your generator is simple and doesn't require a complicated state, using the AnyGenerator
generic is a great way to go.
Now let's use this FibonacciSequence
to solve the kind of math problem that computers are great at.
What if we want to know what is the result of multiplying every number in the Fibonacci sequence under 50? We can try to use a calculator and painstakingly enter in all of the numbers, but it is much more efficient to do it in Swift.
Notice that when we refer to the element type, we must go through the generator type.
We can even add an extension to SequenceType
with a method that will create a limiter for us:
Now, we can easily limit our Fibonacci sequence to only values under 50:
Almost instantaneously, our program will return the result 2,227,680. Now we can really understand why we call these devices computers.
SequenceType
is
In this version of FibonacciSequence
, we create a new generator every time generate is called that takes a closure that does the same thing that our original FibonacciGenerator
was doing. We declare the values
variable outside of the closure so that we can use it to store the state between calls to the closure. If your generator is simple and doesn't require a complicated state, using the AnyGenerator
generic is a great way to go.
Now let's use this FibonacciSequence
to solve the kind of math problem that computers are great at.
What if we want to know what is the result of multiplying every number in the Fibonacci sequence under 50? We can try to use a calculator and painstakingly enter in all of the numbers, but it is much more efficient to do it in Swift.
Notice that when we refer to the element type, we must go through the generator type.
We can even add an extension to SequenceType
with a method that will create a limiter for us:
Now, we can easily limit our Fibonacci sequence to only values under 50:
Almost instantaneously, our program will return the result 2,227,680. Now we can really understand why we call these devices computers.
Notice that when we refer to the element type, we must go through the generator type.
We can even add an extension to SequenceType
with a method that will create a limiter for us:
Now, we can easily limit our Fibonacci sequence to only values under 50:
Almost instantaneously, our program will return the result 2,227,680. Now we can really understand why we call these devices computers.