This article is based on the official Swift5.0 document, it takes about 20 minutes to read, you can have a clear understanding of generics.

What is Generics?

Generics: when you define a type, you place a placeholder in the name of the type to tell the system that the type to be used is uncertain and I’ll take a place. In this way, the system will not report errors when compiling. Then you can really determine the type when you use it.

The language may not be that intuitive, but let’s look at a piece of code to get a sense of what generics are.

// Define a function func that prints variables of any typeprintSomething<T>(value1: T, value2: T) {
    print(value1, value2)
}

printSomething(value1: 1, value2: 2) // Int: 1 2
printSomething(value1: "a", value2: "b") //String: a b
Copy the code

As we can see from the above code, by adding

after the function name to indicate that a generic type has been added, <> tells the compiler that T is a placeholder type and there is no real need to look up the type called T.

Note

  • <T>The T in can be any character or word, but use a capital letter or a hump nomenclature that begins with a capital letter (e.g.V,U,MyTypeParameter).
  • <>You can write not only one type placeholder, but more than one:<T, U>.

Now that we have a rudimentary understanding of what generics are, the question is sure to be asked: Why do we use generics? Let’s take a look at why generics are used.

Why generics

Generic type functions

In our daily work, we often encounter situations where the values of two variables are swapped under certain conditions. If we need to swap two ints, we can easily implement the following function:

func swapTwoIntValue(_ num1: inout Int, _ num2: inout Int) {
    (num1, num2) = (num2, num1)
}

var num1 = 10
var num2 = 20

swapTwoIntValue(&num1, &num2)
print(num1, num2) // 20 10
Copy the code

This function is neat and correct, but what if we also need to swap strings, doubles, and so on? SwapTwoStringValue (_:_:), swapTwoDoubleValue(_:_:)? It’s fine to define two more of these functions, but we’ll see that the internal implementation of all three functions is the same, except that the arguments are of different types. This is where generics come in. We can use generics to write a swap function that uses any Swift primitive type:

func swapTwoValue<T>(_ num1: inout T, _ num2: inout T) {
    (num1, num2) = (num2, num1)
}

var num1 = 10
var num2 = 20

swapTwoValue(&num1, &num2)
print(num1, num2) // 20 10

var str1 = "hello"
var str2 = "world"

swapTwoValue(&str1, &str2)
print(str1, str2) // world hello
Copy the code

Summary – Why generics

  • You can write functions that are more flexible and reusable.
  • Make the code more concise.

Note

  • The aboveswapTwoValue(_:_:)Function is just an example of how generic type functions can be used. If you want to use the function to swap two variables, you can use the official oneswap(_:_:)Function.
  • Note that both variables of the swap function are of typeTAlthough,TIt can represent any type, but both variables must be of the same type. Swift does not allow two variables of different types to swap values, because Swift is a type-safe language.

Now that we know that we can reduce code redundancy by defining functions of generic types, is that all generics are good for? As one of Swift’s most powerful features, it’s certainly not as simple as implementing a generic type function. Now let’s see what else generics can do.

What can we do with it

Implement generic data structures

We can implement a stack that supports multiple types through generics. The specific code is as follows:

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

The Stack can hold multiple types of data, such as Int/String. One thing to note here is that when we extend Stack to evaluate properties or methods, we do not need to declare type parameters; generics in Stack are still valid in Extension. The specific code is as follows:

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

Functions that adhere to arbitrary Type parameters of protocol are implemented through Type Constraints

In daily development, we often need to implement the function of finding the index of a value in an array. If we write the element type of the array dead, we declare the function can only be used for a certain type, what should we do? That’s right, declaring a type as a generic type. From the above introduction, we might write the following code:

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

Binary operator ‘==’ cannot be applied to two ‘T’ operands the Binary operator ‘==’ cannot be applied to two ‘T’ operands. So how to implement this function correctly? This is done using Type Constraints. To do this, change findIndex

to

, which means that T only supports types that implement the Equatable protocol. The specific code is as follows:

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

if let index = findIndex("person0", arr) {
    print("person0 index is \(index)") //person0 index is 0
}
Copy the code

Protocol Indicates the type of the association

Now that we know we can use generics in function parameters, can I implement similar functionality in Protocol? The answer is: of course you can. We can use the associatedType keyword to tell the compiler that the type is generic, and then check its type when we actually use it.

Suppose we want to implement the Protocol of a Container. The Protocol contains the append(_) function to add elements, the count attribute to get the length, and the subscript(_) function to get elements by subscript. If we block out the type of Item, this means that only one type can comply with the Protocol. How do we make more types comply? That’s where the AssociatedType comes in. The following is the specific code:

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

We can make the Stack adhere to this protocol and see how it works. The code is as follows:

extension Stack: Container {
    mutating func append(_ item: Element) {
        push(item)
    }
    
    var count: Int {return items.count }
    
    subscript (_ i: Int) -> Element {
        returnVar s1 = Stack(items: [1,2,3,4]) var s2 = Stack(items: ["a"."b"."c"."d"])
s1.append(5)
print(s1. The items) / / [1, 2, 3, 4, 5]print(s1.count) //5
print(s1[2])    // 3

s2.append("f")
print(s2.items) //["a"."b"."c"."d"."f"]
print(s2.count) //5
print(s2[2])    //"f"
Copy the code

Union types in Protocol add type constraints

We saw above that we can use union types to implement generics in Protocol, so we can also add type constraints to union types to enforce generics to adhere to a Protocol, or to a condition (such as having the same type, etc.). The specific code is as follows:

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

Union types in Protocol add multiple type constraints

We know that we can add a type constraint to the associatedType Item: Equatable to make the Item comply with the Equatable protocol, but what if I want the Item to comply with the Equatable protocol while also restricting it to be of a certain type? This can be done using the WHERE statement. The specific code is as follows:

protocol SuffixableContainer: Container {// This line of code says that the Suffix must comply with SuffixableContainer and its Item type must be the same as the Item type of the Container associatedType Suffix: SuffixableContainerwhereSuffix.Item == Item func Suffix (_ size: Int) -> Suffix} //Stack corresponds to the Suffix above, which complies with 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
    }
}


var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
letSuffix = stackofints.suffix (2) // suffix contains 20 and 30Copy the code

The above code SuffixableContainer implements a piece of data that retrieves a location to the end.

Use type constraints in Extension

As with Protocol, we can implement type constraints with WHERE in Extension. If we did not make Element comply with the Equatable protocol, we would have compiled an error because we used the == operator in this function. The code is as follows:

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

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

Of course, we can also use type constraints when extending Protocol. The code is as follows:

extension Array: Container whereElement: Equatable {} // Extends the Container, and Item is the extension Container that complies with the Equatable protocolwhere Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

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

In addition to forcing generic elements to adhere to a certain protocol, we can also force generic elements to be of a specific type. The code is as follows:

extension Array: Container where Element: Equatable { }

extension Container whereItem == Double {average() -> Double {var sum = 0.0for index in0.. <count { sum += self[index] }return sum / Double(count)
    }
}
print([1260.0, 1200.0, 98.6, 37.0].average()) //648.9
Copy the code

conclusion

That’s all about generics. Now let’s look at the summary of generics.

  • Type placeholders should use uppercase letters or a camel name that begins with a capital case.
  • Generics make code more flexible, reusable and concise.
  • Type parametersorParameters with type constraintsCan be used in generic functions, generic subscripts, generic types.
  • The genericwhereStatement can make your union type comply with a certain protocol or satisfy certain conditions.

This concludes the discussion of generics. I hope you can have a new and profound understanding of generics through this article. Let’s have fun using generics in our projects!

reference

  • Apple Document