The introduction

Continue to learn Swift documents, from the previous chapter: protocol, we learned the content of Swift protocol, mainly with the use of protocol keyword declaration protocol, definition and class, structure, enumeration similar content. Now, let’s learn about the Swift protocol. Due to the long space, here is a section to record, next, let’s begin!

The generic

Generic code enables you to write flexible, reusable functions and types that can use any type based on the requirements you define. You can write code that avoids duplication and expresses its intent in a clear, abstract way.

Generics are one of Swift’s most powerful features, and much of the Swift standard library is built with generic code. In fact, you’ve been using generics throughout the language guide, even if you didn’t realize it. For example, Swift’s array and dictionary types are generic collections. You can create an array that contains Int values, or an array that holds string values, or really any other type of array that you can create in Swift. Similarly, you can create a dictionary to store any value of a given type, and there are no restrictions on what that type can be.

Problems solved by generics

Here is a standard non-generic function swapTwoInts (: 🙂 that swaps two Int values:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
Copy the code

This function uses the in-out argument to swap the values of a and b, as described in the in-out argument.

The swapTwoInts (: 🙂 function swaps the original value of B for a and the original value of A for B. You can call this function to swap values in two Int variables:

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
Copy the code

The swapTwoInts (: 🙂 function is useful, but it can only be used with Int values. If you want to exchange two string values or two Double values, you must write more functions, such as the swaptwotings (: 🙂 and Swaptwodubles (: 🙂 functions shown below:

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}
Copy the code

You may have noticed that the bodies of the Swaptwoint (:), SwaptwotStrings (:), and swapTwoDoubles (:) functions are the same. Only types with double values are accepted.

It is more useful and flexible to write a function that swaps two values of any type. Generic code enables you to write such functions. (The generic versions of these functions are defined below.)

Pay attention to

In all three functions, a and B must be of the same type. If a and B are not of the same type, it is impossible to swap their values. Swift is a type-safe language that does not allow (for example) variables of type String and type Double to swap values with each other. Attempting to do so results in a compile-time error.

2 generic functions

Generic functions can be used with any type. Here is a generic version of the swapTwoInts (: 🙂 function above, called swapTwoValues (: 🙂 :

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
Copy the code

The body of the swapTwoValues (: 🙂 function is the same as the body of the swapTwoInts (: 🙂 function. However, the first line swapTwoValues (: 🙂 is slightly different from swapTwoInts (: :). Here’s a comparison of the first line:

func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
Copy the code

The generic version of the function uses the placeholder type name (T in this case) instead of the actual type name (such as Int, String, or Double). The placeholder type name does not say what t must be, but it does indicate that a and B must be of the same type T, whatever T stands for. Each time the swapTwoValues (: 🙂 function is called, the actual type to be used instead of T is determined. Another difference between generic and non-generic functions is the name of the generic function (swapTwoValues (: :)) followed by the placeholder type name (T) inside Angle brackets (T). The parentheses tell Swift that T is the placeholder type name in the swapTwoValues (: 🙂 function definition. Because T is a placeholder, Swift does not look for an actual type T.

The swapTwoValues (: 🙂 function can now be called just like swapTwoInts, except that you can pass two values of any type, as long as they are of the same type. Each time swapTwoValues (: 🙂 is called, the type for T is inferred from the value type passed to the function.

In the following two examples, T is inferred to be Int and String, respectively:

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello" 
Copy the code

Pay attention to

The swapTwoValues (: 🙂 function defined above is inspired by a generic function called Swap, which is part of the Swift standard library and is automatically provided to you for use in your application. If you need to use the behavior of the swapTwoValues (: 🙂 function in your own code, you can use Swift’s existing swap (: 🙂 function instead of providing your own implementation.

3 Type Parameters

In the swapTwoValues (: 🙂 example above, the placeholder type T is an example of a type parameter. The type parameter specifies and names a placeholder type, immediately after the function name, between a pair of matching Angle brackets (for example, “T”).

Once you specify a type parameter, you can use it to define the type of a function parameter (for example, the a and b parameters of the swapTwoValues (:) function), the return type of the function, or the type annotation in the function body. In each case, the type parameter is replaced with the actual type whenever the function is called. (In the swapTwoValues (: 🙂 example above, T is replaced with Int on the first call to the function and String on the second.) Multiple type parameters can be provided by separating multiple type parameter names with commas inside Angle brackets.

4 Named type parameters

In most cases, type parameters have descriptive names, such as Key and Value in Dictionary<Key, Value> and Element in Array (Element), which tell the reader how a type parameter relates to the generic type or function it uses. However, when there is no meaningful relationship between them, it is traditional to name them with single letters such as T, U, and V, as in the T in the swapTwoValues (: 🙂 function above.

Pay attention to

Always provide uppercase names for type parameters (such as T and MyTypeParameter) to indicate that they are placeholders for the type and not values.

5 Generic types

In addition to generic functions, Swift allows you to define your own generic types. These are custom classes, structs, and enumerations that can be used with any type in a similar way to arrays and dictionaries.

This section describes how to write a generic collection type called Stack. A stack is an ordered set of values, similar to an array, but with a more restricted set of operations than Swift’s array type. Arrays allow you to insert and delete new items anywhere in the array. However, the stack only allows new items to be appending to the end of the collection (called pushing new values onto the stack). Similarly, the stack only allows items to be removed from the end of the collection (called popping a value from the stack).

Pay attention to

The UINavigationController class uses the concept of a stack to model a view controller in its navigation hierarchy. Call the UINavigationController class pushViewController (: Animated:) method to add (or push) view controllers to the navigation stack, And call its popViewControllerAnimated (:) method is removed from the navigation stack or pop-up view controller. The stack is a useful collection model whenever you need a strict last in, first out approach to managing collections.

The following figure shows the push and pop behavior of the stack:

1. There are currently three values on the stack. The fourth value is pushed to the top of the stack. 3. The stack now holds four values, with the nearest one at the top. 4. The top item in the stack pops up. 5. After a value pops up, the stack holds three values again.

Here’s how to write a non-generic version of the stack, in this case, for Int values:

struct IntStack {
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}
Copy the code

This structure uses an array property called items to store values on the stack. Two on-stack and pop-off and push-off methods are provided. These methods are marked as variants because they require modifying (or mutating) the structured items array.

However, the IntStack type shown above can only be used with Int values. It would be more useful to define a generic stack class that can manage a stack of any type of value. Here is a generic version of the same code:

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
Copy the code

Notice that the generic version of the stack is essentially the same as the non-generic version, but its type parameter is named Element, not the actual Int type. This type parameter is written in Angle brackets (elements) after the structure name. The element defines a placeholder name for the type to be supplied later. This future type can be called an element anywhere in the structure definition. In this case, the element is used as a placeholder in three places:

  • Create a property called Items that is initialized with a null-valued array of type Element
  • Specify that the push (:) method has an argument named item that must be of type Element
  • Specify that the pop () method will return a value of type Element

Because it is a generic type, Stack can be used to create a Stack of any valid type in Swift in a similar way to Array and Dictionary.

A new stack instance can be created by writing the type to be stored on the stack inside Angle brackets. For example, to create a new String stack, write stack<String> () :

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings
Copy the code

Here’s what stackOfStrings looks like after pushing these four values onto the stack:

Popping a value from the stack will remove and return the top value “cuatro” :

let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings
Copy the code

Here’s what the top of the stack looks like when it pops up:

Extend generic types

When you extend a generic type, you do not provide a list of type parameters as part of the extension definition. Instead, the list of type parameters in the original type definition is available in the body of the extension, and the original type parameter name is used to refer to the type parameters in the original definition.

The following example extends the generic stack type to add a read-only computed property called topItem that returns the topItem on the stack without popping it off the stack:

extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}
Copy the code

The topItem property returns an optional value of type Element. If the stack is empty, topItem returns nil; If the stack is not empty, topItem returns the last item in the items array.

Note that this extension does not define a list of type parameters. Instead, the existing type parameter name Element of the stack type is used in the extension to indicate the optional type of the topItem evaluated attribute. The topItem computed property can now be used with any stack instance to access and query its top-level item without removing it.

if let topItem = stackOfStrings.topItem { print("The top item on the stack is \(topItem).") } // Prints "The top item on  the stack is tres."Copy the code

Extensions of generic types can also include requirements that instances of the extension type must meet in order to obtain new functionality, as discussed below in extensions using the generic Where clause.

7 Type Constraints

The swapTwoValues (: 🙂 function and stack type can be used for any type. However, it is sometimes useful to enforce certain type constraints on types that can be used with generic functions and generic types. Type constraints specify that type parameters must inherit from a particular class or conform to a particular protocol or combination of protocols.

For example, Swift’s dictionary type limits the types that can be used as dictionary keys. As stated in the dictionary, the key type of the dictionary must be hashed. That is, it must provide a way to make itself uniquely representative. A Dictionary needs its keys to be hashed so that it can check if it already contains the value of a particular key. Without this requirement, Dictionary has no way to determine whether a value for a particular key should be inserted or replaced, or to find a value for a given key that already exists in the Dictionary.

This requirement is implemented by a Type constraint on the Dictionary’s key type, which specifies that the key type must conform to the Hashable protocol, a special protocol defined in the Swift standard library. By default, all of Swift’s primitive types, such as String, Int, Double, and Bool, are hashed. For information about making your own custom types hash compliant, see Conforming to the Hash Protocol.

You can define your own type constraints when creating custom generic types, which provide the power of generic programming. Abstract concepts such as hashes describe types in terms of conceptual characteristics rather than concrete types.

7.1 Type Constraint Syntax

Type constraints are written by placing a single class or protocol constraint after the name of the type parameter (separated by a colon) as part of the type parameter list. The basic syntax for type constraints on generic functions looks like this (although the syntax for generic types is the same) :

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}
Copy the code

The above hypothetical function takes two type arguments. The first type parameter T has a type constraint that requires T to be a subclass of SomeClass. The second type parameter U has a type constraint that requires U to conform to the protocol SomeProtocol.

7.2 Type Constraint Practices

This is a non-generic function called findIndex (ofString:in:), which is given a string value to look up and an array ofString values to look up in. The findIndex(ofString:in:) function returns an optional Int that, if found, will be the first index in the array to match the string; If the string cannot be found, nil is returned:

func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
Copy the code

The findIndex(ofString:in:) function can be used to find a string value in an array of strings:

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
    print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"
Copy the code

However, the principle of finding an index of a value in an array is not only useful for strings. You can write the same functionality as a generic function by replacing a string with a value of some type. Here’s what you’d expect from a generic version of findIndex (ofString:in:), called findIndex (of :in:), to be written. Notice that the return type of this function is still Int, okay? Because the function returns an optional index number instead of an optional value in the array. Note that although this function does not compile, for reasons explained by the following example:

func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
Copy the code

This function is not compiled as written above. The problem is the equality check, “if value==valueToFind”. Not every type in Swift can be compared with the equal operator (==). For example, if you create your own class or structure to represent a complex data model, Swift cannot guess for you what that class or structure means “equal.” Therefore, it is impossible to guarantee that this code will work for all possible types of T, and an appropriate error will be reported when you try to compile the code.

All is not lost, however. The Swift standard library defines a protocol called Equatable, which requires any consistent type to implement the equal operators (=) and not equal operators (! Compare any two values of the type. All Swift standard types automatically support the Equatable protocol.

Any equivalent type can safely be used with the findIndex (of:in:) function because it is guaranteed to support the equal operator. To express this fact, the Equatable’s type constraint can be defined as part of the type parameter definition when defining the function:

func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
Copy the code

The single type parameter of findIndex (of:in:) is written as T:equalable, which means “any type T conforming to the Equalable protocol.”

The findIndex (of:in:) function can now compile successfully and can be used for any type that can be equal, such as Double or String:

Let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25] // doubleIndex is an optional Int with no value, Because 9.3 isn't in the array let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"]) // stringIndex is an optional Int containing a value of 2Copy the code

8 Association Type

When defining a protocol, it is sometimes useful to declare one or more associated types as part of the protocol definition. An association type provides a placeholder name for the type used as part of the protocol. The actual type to be used for the association type is not specified until the protocol is adopted. The association type is specified with the associatedType keyword.

8.1 Association Type Practice

Here is an example of a protocol called Container that declares an association type named Item:

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
Copy the code

The container protocol defines three essential functions that any container must provide:

  • You must be able to add new items to the container using the append (:) method.
  • You must be able to access the count of items in the container through the count property that returns an Int value.
  • You must be able to retrieve each item in the container using a subscript that takes an Int index value.

This protocol does not specify how items in the container should be stored or what types they should be allowed. The protocol specifies only the three-bit functionality that any type must provide to be considered a container. A consistent type can provide additional functionality as long as it meets these three requirements.

Any type that conforms to the container protocol must be able to specify the type of the value it stores. Specifically, it must ensure that only items of the correct type are added to the container, and it must be clear about the type of items returned by their subscripts.

To define these requirements, the container protocol needs a way to refer to the type of the element the container will hold without knowing the type of the particular container. The container protocol needs to specify that the type of any value passed to the append (:) method must be the same as the element type of the container, and that the subscript of the container will return the same value as the element type of the container.

To do this, the container protocol declares an association type named Item and writes it as AssociatedType Item. The protocol does not define what items are left for information to be provided by any consistent type. However, item aliases provide a way to refer to the type of an item in a container and define a type for append (:) methods and subscripts to ensure that the expected behavior of any container is enforced.

Here is a version of the non-generic IntStack type from the generic type above, adjusted to conform to the container protocol:

struct IntStack: Container {
    // original IntStack implementation
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // conformance to the Container protocol
    typealias Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}
Copy the code

The IntStack type implements all three requirements of the container protocol, and in each case wraps a portion of the existing functionality of the IntStack type to meet these requirements.

In addition, IntStack specifies that the appropriate item to use for this implementation of the container is of type Int. The definition of TypeAlias Item=Int converts the abstract Item type to a concrete Int to implement the container protocol.

Because of Swift’s type inference, it is not really necessary to declare the specific item of Int as part of the IntStack definition. Because IntStack complies with all the requirements of the container protocol, Swift can infer the appropriate Item to use by looking at the type of the Item argument to the append (:) method and the return type of the subscript. In fact, if you remove the TypeAlias Item=Int line from the above code, everything is still valid because it is clear what type to use for Item.

It is also possible to make the generic stack type conform to the container protocol:

struct Stack<Element>: Container {
    // original Stack<Element> implementation
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // conformance to the Container protocol
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}
Copy the code

This time, the type parameter element is used as the type of the item parameter and the return type of the subscript for the append (:) method. Thus, Swift can infer that Element is the appropriate type of item for this particular container.

8.2 Extend an existing type to specify an association type

You can extend an existing type to add consistency to a protocol, as described in Adding protocol consistency using extensions. This includes a protocol with an association type.

Swift’s array type already provides an append (:) method, a count attribute, and a subscript with an Int index to retrieve its elements. These three capabilities meet the requirements of the container protocol. This means that you can extend an Array to conform to the container protocol by declaring the Array adoption protocol. Perform this operation using an empty extension, as described in using the Extension Declaration Protocol adoption:

extension Array: Container {}
Copy the code
8.3 Adding Constraints to an Association Type

You can add type constraints to an association type in a protocol to require a consistent type to satisfy these constraints. For example, the following code defines a version of the container that requires items in the container to be equal.

protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
Copy the code

To qualify for this version of the container, the item type of the container must conform to the Equatable protocol.

8.4 Using protocols in Association Type constraints

Protocols can emerge as part of their own requirements. For example, here’s a protocol that improves the container protocol by adding a requirement for the suffix (:) method. The suffix (:) method returns a given number of elements from the end of the container and stores them in instances of type suffix.

protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}
Copy the code

In this protocol, Suffix is an associated type, like the Item type in the container example above. Suffix has two constraints: it must conform to the SuffixableContainer protocol (the one currently being defined), and its Item type must be the same as the container’s Item type. The constraint on Item is a generic WHERE clause, as discussed below in the association type of the generic WHERE clause.

The following stack type extends from the generic type above, which adds consistency to the SuffixableContainer protocol:

extension Stack: SuffixableContainer { func suffix(_ size: Int) -> Stack { var result = Stack() for index in (count-size).. <count { result.append(self[index]) } return result } // Inferred that Suffix is Stack. } var stackOfInts = Stack<Int>()  stackOfInts.append(10) stackOfInts.append(20) stackOfInts.append(30) let suffix = stackOfInts.suffix(2) // suffix contains 20 and 30Copy the code

In the example above, the Suffix correlation type of the Stack is also Stack, so the Suffix operation on the Stack returns another Stack. Alternatively, SuffixableContainer types can have a different Suffix type than themselves, meaning that the Suffix operation can return a different type. For example, here’s an extension to the non-generic IntStack type, which adds suffexablecontainer consistency, using Stack<\Int> as the Suffix type instead of IntStack:

extension IntStack: SuffixableContainer { func suffix(_ size: Int) -> Stack<Int> { var result = Stack<Int>() for index in (count-size).. <count { result.append(self[index]) } return result } // Inferred that Suffix is Stack<Int>. }Copy the code

Generic WHERE clause

Type constraints, as described in type constraints, enable you to define requirements for type parameters associated with generic functions, subscripts, or types.

The requirement to define an association type is also useful. You can do this by defining a generic WHERE clause. The generic WHERE clause enables you to require that the association type must conform to a particular protocol, or that some type parameters and the association type must be the same. The generic WHERE clause begins with the WHERE keyword, followed by a constraint on the associated type or an equality relationship between the type and the associated type. Write a generic WHERE clause before the opening brace of the type or function body.

The following example defines a generic function called allItemsMatch that checks whether two container instances contain the same items in the same order. If all items match, the function returns a Boolean value of true; If there is no match, return false.

The two containers to check need not be the same type of container (although they can be), but they must contain items of the same type. This requirement is expressed through a combination of type constraints and generic WHERE clauses:

func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable { // Check that both containers contain the same number of items. if someContainer.count ! = anotherContainer.count { return false } // Check each pair of items to see if they're equivalent. for i in 0.. <someContainer.count { if someContainer[i] ! = anotherContainer[i] { return false } } // All items match, so return true. return true }Copy the code

This function takes two arguments, called someContainer and anotherContainer. The someContainer parameter is of type C1 and the other container parameter is of type C2. C1 and C2 are both type parameters for the two container types to be determined when calling the function.

The two type parameters of the function have the following requirements:

  • C1 must comply with the Container protocol (written C1:Container).
  • C2 must also comply with the Container protocol (written C2:Container).
  • Items for C1 must be the same as items for C2 (c1.item == c2.item).
  • Items for C1 must conform to the equality protocol (written c1. Item: Equatable).

The first and second requirements are defined in the function’s type parameter list, and the third and fourth requirements are defined in the function’s generic WHERE clause.

These requirements mean:

  • SomeContainer is a C1 container.
  • AnotherContainer is a C2 container.
  • SomeContainer and anotherContainer contain the same type of project.
  • Items in someContainer can be used with the unequal operator (! =) to see if they differ from each other.

The combination of the third and fourth requirements means that items in another container can also be used! = operator, because they are exactly the same type as the items in someContainer. These requirements enable the allItemsMatch (: 🙂 function to compare two containers, even if they are different container types.

The allItemsMatch (: 🙂 function first checks whether both containers contain the same number of items. If they contain a different number of items, they cannot be matched and the function returns false. Once this is done, the function uses the for in loop and the half-open range operator (… <) Iterate over all items in someContainer. For each item, the function checks whether the item from one container is not equal to the corresponding item in another container. If the two items are not equal, the two containers do not match, and the function returns false.

If no mismatch is found at the end of the loop, the two containers match and the function returns true.

Here is an implementation of allItemsMatch(::) :

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")

var arrayOfStrings = ["uno", "dos", "tres"]

if allItemsMatch(stackOfStrings, arrayOfStrings) {
    print("All items match.")
} else {
    print("Not all items match.")
}
// Prints "All items match."
Copy the code

The above example creates a stack instance to store string values and pushes three strings onto the stack. The example also creates an array instance that is initialized with array text of the same three strings as the stack. Even though the stack and array types differ, they both conform to the container protocol and both contain the same type of value. Therefore, the allItemsMatch (: 🙂 function can be called with these two containers as arguments. In the example above, the allItemsMatch (: 🙂 function correctly reports that all items in both containers match.

Extend the generic WHERE clause

You can also use the generic WHERE clause as part of the extension. The following example extends the generic stack structure from the previous example to add an isTop (:) method.

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}
Copy the code

The isTop(_:) method is first compared to the given item (). If you try to do this without a generic WHERE clause, you run into a problem: the isTop (:) implementation uses the == operator, but the stack definition does not require its entries to be equal, so using the == operator causes a compile-time error. You can use the generic where clause to add new requirements to an extension so that the extension adds an isTop (:) method only when items in the stack are equal.

Here is the isTop (:) method in action:

if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// Prints "Top element is tres."
Copy the code

If you try to call the isTop (:) method on a stack with unequal elements, you will get a compile-time error.

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)  // Error
Copy the code

You can use a generic WHERE clause with a protocol extension. The following example extends the container protocol from the previous example to add the startsWith (:) method.

extension Container where Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}
Copy the code

The startsWith (:) method first ensures that the container has at least one item, and then checks whether the first item in the container matches the given item. This new startsWith (:) method can be used with any type conforming to the container protocol, including the stack and array used above, as long as the container items are equal.

if [9, 9, 9].startsWith(42) {
    print("Starts with 42.")
} else {
    print("Starts with something else.")
}
// Prints "Starts with something else."
Copy the code

The generic WHERE clause in the above example requires Item to conform to the protocol, but you could also write a generic WHERE clause that requires Item to be of a specific type. Such as:

Extension Container where Item == Double {func average() -> Double {var sum = 0.0 for index in 0.. <count {sum += self[index]} return sum/Double(count)}} print([1260.0, 1200.0, 98.6, 37.0] business ()) / / Prints "648.9"Copy the code

This example adds the Average () method to a container with an item type of Double. It iterates over the items in the container to add them, then divides by the number of containers to calculate the average. It explicitly converts count from Int to Double to enable floating-point division.

You can include multiple requirements in a generic WHERE clause as part of an extension, just like a generic WHERE clause written elsewhere. Separate each requirement in the list with a comma.

Context-specific generic Where clause

When you are already working in the context of generic types, you can write a generic WHERE clause as part of a declaration that does not have its own generic type constraints. For example, you can write a generic WHERE clause on an index of a generic type or on a method in a generic type extension. The container structure is generic, and the WHERE clause in the following example specifies which type constraints must be met for these new methods to be available on the container.

Extension Container {func average() -> Double where Item == Int {var sum = 0.0 for index in 0.. <count { sum += Double(self[index]) } return sum / Double(count) } func endsWith(_ item: Item) -> Bool where Item: Equatable { return count >= 1 && self[count-1] == item } } let numbers = [1260, 1200, 98, 37] print(numbers. Average ()) // Prints "648.75" print(numbers.Copy the code

This example adds the average () method to the container when the items are integers and the endsWith (:) method when the items can be equal. Both functions contain a generic WHERE clause that adds type constraints to the generic item type parameters originally declared by the container.

If you want to write this code without the context WHERE clause, you need to write two extensions, each corresponding to a generic WHERE clause. The example above has the same behavior as the example below.

Extension Container WHERE Item == Int {func average() -> Double {var sum = 0.0 for index in 0.. <count { sum += Double(self[index]) } return sum / Double(count) } } extension Container where Item: Equatable { func endsWith(_ item: Item) -> Bool { return count >= 1 && self[count-1] == item } }Copy the code

In this example version of the context where clause, the implementations of average () and endsWith (:) are in the same extension, because each method’s generic where clause declares the requirements to be satisfied to make the method usable. Moving these requirements into the extended generic WHERE clause makes the methods available in the same case, but each requirement requires an extension.

Generic Where clause for association type

You can include a generic WHERE clause on an association type. For example, suppose you want to make a version of the container that contains iterators, like the sequence protocol used in the standard library. Here’s what you wrote:

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }

    associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
    func makeIterator() -> Iterator
}
Copy the code

The generic WHERE clause on an iterator requires that the iterator must iterate over elements of the same item type as the container item, regardless of the iterator type. The iterator () function provides access to a container iterator.

For protocols inherited from another protocol, you can add constraints to the inherited association type by including the generic WHERE clause in the protocol declaration. For example, the following code declares a ComparableContainer protocol that requires the item to be Comparable:

protocol ComparableContainer: Container where Item: Comparable { }
Copy the code

Generic subscript

Subscripts can be generic and can include a generic WHERE clause. Write the placeholder type name in Angle brackets after the subscript, and write a generic WHERE clause before the opening brace of the body of the subscript. Such as:

extension Container {
    subscript<Indices: Sequence>(indices: Indices) -> [Item]
        where Indices.Iterator.Element == Int {
            var result = [Item]()
            for index in indices {
                result.append(self[index])
            }
            return result
    }
}
Copy the code

This extension to the container protocol adds a subscript that takes a series of indexes and returns an array containing the items in each given index. The constraints of general subscripts are as follows:

  • The generic parameter index in Angle brackets must be of a type conforming to the sequence protocol in the standard library.
  • The subscript takes an index argument, which is an instance of the index type.
  • The generic WHERE clause requires that the iterator of the sequence must traverse an element of type Int. This ensures that the index in the sequence is of the same type as the index used for the container.

Taken together, these constraints mean that the value passed for the index parameter is a sequence of integers.

Reference documentation: Swift-Generics