Extending generics

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.

Adding methods to all forms of a generic

Knowing that an array is declared as struct Array<Element>, your first instinct to extend an array might look something similar to this:

extension Array<Element> { // Use of undeclared type 'Element'
    // ...
}

However, as you can see, you would get an error. Instead, you can simply leave out the placeholder specification and still use the Element placeholder inside your implementations. Your other instinct might be to declare Element as a placeholder for your individual methods:

extension Array {
    func someMethod<Element>(element: Element) {
        // ...
    }
}

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:

extension Array {
    mutating func addElement<Element>(element: Element) {
        self.append(element)
        // Cannot invoke 'append' with argument list
        // of type '(Element)'
    }
}

This is because the Element defined in Array cannot be guaranteed to be the exact same type as the new Element defined in addElement:. You are free to declare additional placeholders in methods on generic types, but it is best to give them unique names so that they don't hide the type's version of the placeholder.

Now that we understand this, let's add an extension to the array that allows us to test if it contains an element passing a test:

extension Array {
    func hasElementThatPasses(
        test: (element: Element) -> Bool
        ) -> Bool
    {
        for element in self {
            if test(element: element) {
                return true
            }
        }
        return false
    }
}

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.

Adding methods to only certain instances of a generic

A constraint on an extension is written as a where clause, as shown:

extension Array where Element: Equatable {
    func containsElement(element: Element) -> Bool {
        for testElement in self {
            if testElement == element {
                return true
            }
        }
        return false
    }
}

Here we add a constraint that guarantees that our element is equatable. This means that we will only be able to call this method on arrays that have equatable elements:

[1,2,3,4,5].containsElement(4) // true
class MyType {}
var typeList = [MyType]()
typeList.containsElement(MyType()) // Type 'MyType' does not
// conform to protocol 'Equtable'

Again, Swift is protecting us from accidently trying to call this method on an array that it wouldn't work for.

These are the building blocks that we have to play with generics. However, we actually have one more feature of protocols that we have not discussed, which works really well in combination with generics.

Extending protocols

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 adds a method to all types that implement the Comparable. This means that it will now be available on any of the built-in types that are comparable and any of our own types that are comparable:

6.isBetween(4, b: 7) // true
"A".isBetween("B", b: "Z") // false

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

Here, even though the building variable is defined as a Building, it is in fact a house; so Swift will call the house's version of clean. The contrast with protocol extensions is that it will call the version of the method that is defined by the exact type the variable is declared as:

protocol Building {
    var squareFootage: Int {get}
}

extension Building {
    func clean() {
        print(
            "Scrub (self.squareFootage) square feet of floors"
        )
    }
}

struct House: Building {
    let squareFootage: Int
    let numberOfBedrooms: Int
    let numberOfBathrooms: Double

    func clean() {
        print("Make (self.numberOfBedrooms) beds")
        print("Clean (self.numberOfBathrooms) bathrooms")
    }
}

let house = House(
    squareFootage: 1000,
    numberOfBedrooms: 2,
    numberOfBathrooms: 1.5
)
house.clean()
// Make 2 beds
// Clean 1.5 bathrooms

(house as Building).clean()
// Scrub 1000 square feet of floors

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.

All of this shows that it can be hard to choose between using structures and protocols or class inheritance. We will look at the last piece of that consideration in the next chapter on memory management, so we will be able to make a fully informed decision when moving forward.

Now that we have looked at the features available to us with generics and protocols, let's take this opportunity to explore some more advanced ways protocols and generics are used in Swift.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset