Generic code allows you to write out functions and types that are suitable for any type, flexible and reusable, based on custom requirements. It allows you to avoid duplication of code and to express the intent of the code in a clear and abstract way.
Generics are one of the most powerful features of Swift, and many of the Swift standard libraries are built from generic code. In fact, generics are used throughout the entire language handbook, but you may not find it. For example, Swift's Array and Dictionary are generic collections. You can create an array of Int, or you can create a String array, or even an array of any other Swift type. Similarly, you can create a dictionary that stores any given type.
problems solved by generics
The following is a standard Non-generic function swaptwoints (_:_:), which is used to exchange two INT values:
Func swaptwoints (InOut a:int, InOut _ B:int) {Let
Temporarya = a
a = b
b = Temporarya
}
This function uses the input and output parameters (InOut) to exchange the values of A and B, please refer to the input and output parameters.
Swaptwoints (_:_:) The function swaps the original value of B to a and swaps the original value of a to B. You can call this function to exchange the values of two Int variables:
var someint = 3
var anotherint =
swaptwoints (&someint, &anotherint)
print (Someint is now \ Someint), and Anotherint is now (anotherint) ")
//print" Someint is now, and Anotherint are now 3 "
True, Swaptwoints (_:_:) A function is useful, but it can only exchange Int values, and if you want to swap two String values or double values, you have to write more functions, such as swaptwostrings (_:_:) and Swaptwodoubles (_:_:) as follows:
Func swaptwostrings (InOut a:string, InOut _ b:string) {Let
Temporarya = a
a = b
b = Temporarya
}
Fu NC swaptwodoubles (InOut a:double, InOut _ b:double) {Let
Temporarya = a
a = b
b = Temporarya
}
You may notice swaptwoints (_:_:), Swaptwostrings (_:_:) and Swaptwodoubles (_:_:) function is the same, except that the variable types passed in are Int, String, and Double, respectively.
In practical applications, a more practical and flexible function is often needed to exchange two of values of any type, and fortunately, generics code helps you solve the problem. (The generic versions of these functions are already defined below.) )
Attention
In the above three functions, the type A and B are the same. If the type A and B are different, they cannot exchange values. Swift is a type-safe language, so it does not allow a variable of type String and a Double type to interchange values. Attempting to do so will result in a compilation error.
Generic Functions
Generic functions can be applied to any type, and the following swaptwovalues (_:_:) The function is a generic version of the top three functions:
Func swaptwovalues<t> (InOut a:t, InOut _ B:t) {Let
Temporarya = a
a = b
b = Temporarya
}
Swaptwovalues (_:_:) The function body and swaptwoints (_:_:) Functions are the same, they are only a little different in the first row, as follows:
Func swaptwoints (InOut a:int, InOut _ B:int)
func swaptwovalues<t> (inout a:t, InOut _ B:t)
The generic version of this function replaces the actual type name (such as Int, String, or Double) with the placeholder type name, which is represented here with the letter T. The placeholder type name does not indicate what type T must be, but it indicates that A and B must be the same type T, regardless of what type T represents. Only Swaptwovalues (_:_:) When a function is invoked, the type that T represents can be determined based on the actual type being passed in.
Another difference is that the generic function name (Swaptwovalues (_:_:) follows the placeholder type name (T) and is enclosed in angle brackets (<T>). This angle bracket tells Swift that T is Swaptwovalues (_:_:) A placeholder type name within a function definition, so Swift does not look for the actual type named T.
Swaptwovalues (_:_:) Functions can now be like swaptwoints (_:_:) That way, the difference is that it can accept two values of any type, provided the two values have the same type. Swaptwovalues (_:_:) When a function is invoked, the type that T represents is inferred from the type of the value passed in.
In the following two examples, T represents Int and String respectively:
var someint = 3
var anotherint =
swaptwovalues (&someint, &anotherint)
//Someint is now, and a Notherint is now 3
var somestring = "Hello"
var anotherstring = "World"
swaptwovalues (&somestring, & anotherstring)
//somestring is now ' world ', and anotherstring is now ' hello '
Attention
Swaptwovalues as defined above (_:_:) function is subject to swap (_:_:) function to inspire and implement. The latter exists in the Swift standard library, and you can use it in your application. If you need similar swaptwovalues in your code (_:_:) function, you can use the existing swap (_:_:) Function.
type Parameters
Swaptwovalues in the above (_:_:) In the example, the placeholder type T is an example of a type parameter. The type parameter specifies and names a placeholder type and is immediately followed by the function name, surrounded by a pair of angle brackets (for example, <T>).
Once a type parameter is specified, you can use it to define the parameter type of a function (for example, Swaptwovalues (_:_:) The arguments A and B in the function, or as the return type of the function, can also be used as the annotation type in the body of the function. In these cases, the type parameter is replaced by the actual type when the function is called. (In the above Swaptwovalues (_:_:) In the example, when the function is first invoked, T is replaced by Int, and the second call is replaced by a String. )
You can provide multiple type parameters, which are written in angle brackets and separated by commas.
named type parameters
In most cases, the type parameter has a descriptive name, such as Dictionary<key, Key and Value in Value>, and Element in array<element>. This can tell people who read the code the relationship between these type parameters and the generic functions. However, when there is no meaningful relationship between them, they are usually named with a single letter, such as T, U, V, as shown above Swaptwovalues (_:_:) The same as the T in the function.
Attention
Always use the Hump naming method (such as T and Mytypeparameter) at the beginning of uppercase letters to name the type parameters to indicate that they are placeholder types, not a value.
generic type
In addition to generic functions, Swift also allows you to define generic types. These custom classes, structs, and enumerations can be applied to any type, similar to Array and Dictionary.
This section will show you how to write a generic collection type called stack (stack). Stacks are ordered sets of values, similar to array, but have more operational limitations than Swift's array type. Arrays allow you to insert new elements anywhere in an array, or to delete elements from any location. The stack only allows the addition of new elements (called stacks) at the end of the collection. Similarly, the stack can only remove elements from the end (called the stack).
Attention
The concept of stacks has been used by the Uinavigationcontroller class to construct the navigation structure of the view controller. You by calling Uinavigationcontroller's Pushviewcontroller (_:animated:) method to add a new view controller to the navigation stack, via popviewcontrolleranimated (_:) method to remove the view controller from the navigation stack. Whenever you need a strict "LIFO" approach to managing collections, stacks are the most useful models.
The following illustration shows the behavior of the stack (push) and Stack (POP):
There are now three values in the stack. The fourth value is pressed into the top of the stack. Now there are four values on the stack, and the nearest stack is at the top. The topmost value in the stack is removed, or called a stack. After removing a value, the stack now has only three values.
Here's how to write a non-generic version of a stack with an Int-type stack as an example:
struct IntStack {
var items = [Int] ()
mutating func push (item:int) {
items.append (item)
}
mutating Func pop ()-> Int {return
items.removelast ()
}
}
This structure uses an Array property named items in the stack to store the value. Stack provides two methods: Push (_:) and Pop () to push values into the stack and remove values from the stack. These methods are marked as mutating because they need to modify the items array of the structure body.
The intstack structure above can only be used for Int types. However, you can define a generic Stack structure body so that you can handle any type of value.
The following 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 ()
}
}
Note that the Stack is basically the same as IntStack, but replaces the actual Int type with the placeholder type parameter Element. This type parameter is wrapped in a pair of angle brackets (<Element>) immediately following the name of the structure.
Element defines a placeholder name for the type to be provided. The type to be provided can be referenced by Element in the definition of the struct body. In this example, the Element is used as a placeholder in the following three places: Create the Items property and initialize it with an empty array of Element types. Specify push (_:) The type of the unique parameter item for a method must be an Element type. Specifies that the return value type of the Pop () method must be an Element type.
Because stack is a generic type, it can be used to create any valid type of stack in Swift, as Array and Dictionary do.
You can create and initialize a stack instance by writing out the data types that you want stored in the stack in angle brackets. For example, to create a stack of type String, you can write Stack<string> ():
var stackofstrings = stack<string> ()
stackofstrings.push ("Uno")
Stackofstrings.push ("dos")
Stackofstrings.push ("tres")
Stackofstrings.push ("cuatro")
//Stack now has 4 strings
The following figure shows how stackofstrings the four values into the stack:
Remove and return the value "Cuatro" at the top of the stack, coming out of the stack:
Let Fromthetop = Stackofstrings.pop ()
//Fromthetop value is "cuatro" and now there are 3 strings in the stack
The following figure shows how stackofstrings the top value out of the stack:
extending a generic type
When you extend a generic type, you do not need to provide a list of type parameters in the definition of extension. The list of type parameters declared in the original type definition can be used directly in the extension, and the name of the parameter from the original type is used as a reference to the type parameter in the original definition.
The following example extends the generic type stack by adding a read-only computed property named Topitem, which returns the element at the top of the current stack without removing it from the stack:
Extension Stack {
var topitem:element? {return
items.isempty? Nil:items[items.count-1]
}
}
The Topitem property returns an optional value for an Element type. When the stack is empty, Topitem returns nil, and when the stack is not empty, Topitem returns the last element in the items array.
Note that this extension does not define a list of type parameters. Conversely, an existing type parameter name Element of the Stack type is used in the extension to represent the optional type of the computed attribute Topitem.
The computed property Topitem can now be used to access the top element of any Stack instance and not remove it:
If let Topitem = stackofstrings.topitem {
print (' The top item on the ' stack is \ (topitem). ')
}
Print "The top item" in the stack is tres.
type constraint
Swaptwovalues (_:_:) Functions and Stack types can work on any type. However, sometimes it is useful to add a specific type constraint to a type that is used in a generic function and in a generic type. A type constraint can specify that a type parameter must inherit from a specified class, or conform to a specific protocol or protocol combination.
For example, Swift's Dictionary type restricts the type of the dictionary's keys. In the dictionary description, the dictionary's key type must be hash (hashable). In other words, there must be a way to represent it uniquely. The Dictionary key is hashed to make it easier to check whether the dictionary already contains a particular key value. Without this requirement, Dictionary will not be able to determine whether the value of a specified key can be inserted or replaced, or the value of a specified key that is already stored in the dictionary.
To implement this requirement, a type constraint is forced to be added to the Dictionary key type, requiring its key type to conform to the Hashable protocol, a specific protocol defined in the Swift standard library. All Swift basic types (for example, String, Int, Double, and Bool) are hashed by default.
When you create custom generic types, you can define your own type constraints, which will provide more powerful generic programming capabilities. Abstract concepts, such as hashes, describe the conceptual characteristics of types, not their explicit types.
type constraint Syntax
You can place a class name or protocol name after a type parameter name and separate it with a colon to define the type constraints, which will become part of the type argument list. The basic syntax for adding a type constraint to a generic function is as follows (the syntax for a generic type is the same as when it is used):
Func Somefunction<t:someclass, u:someprotocol> (Somet:t, someu:u) {
//here is the function body part of the generic function
}
The above function has two type parameters. The first type parameter T has a requirement T to be a type constraint of the SomeClass subclass; the second type parameter u has a requirement that u must conform to the type constraint of the Someprotocol protocol.
type constraint Practice
Here is a non-generic function called Findstringindex, which functions to find the index of the given string value in a string array. If a matching string is found, Findstringindex (_:_:) function returns the index value of the string in the array, otherwise returns nil:
Func findstringindex (array: [String], _ valuetofind:string)-> Int? {
for (index, value) in Array.enumerate () {
if value = = Valuetofind {return
index
}
} return
Nil
}
Findstringindex (_:_:) function can be used to find a string in an array of strings:
Let strings = ["Cat", "dog", "llama", "Parakeet", "terrapin"]
if let Foundindex = Findstringindex (Strings, "llama") { C1/>print ("The" The index of Llama is \ (Foundindex))
}
//print "The index of Llama is 2"
It is not useful to find only the index of a string in an array. However, you can replace the String type with a placeholder type T to write out a generic function findindex (_:_:) with the same functionality.
The following shows the Findstringindex (_:_:) The generic version of the function, FindIndex (_:_:). Notice that the type of the return value for this function is still Int, because the function returns an optional number of indexes instead of an optional value from the array. The caveat is that this function cannot be compiled, because it is explained in the following example:
Func findindex<t> (array: [T], _ Valuetofind:t)-> Int? {
for (index, value) in Array.enumerate () {
if value = = Valuetofind {return
index
}
} return
ni L
}
The function written above cannot be compiled. The problem is in the equality check, that is, "if value = = Valuetofind". Not all Swift types can be compared using the equality character (= =). For example, if you create a custom class or struct to represent a complex data model, Swift cannot guess what "equality" means for that class or struct. For this reason, this part of the code is not guaranteed to apply to every possible type T, and when you try to compile this part of the code, you get a corresponding error.
However, all of these will not let us start. The Swift standard library defines a equatable protocol that requires that any type that follows the protocol must implement an equality character (= =) and an inequality (!=) so that any two values of that type can be compared. All the Swift standard types automatically support the Equatable protocol.
Any equatable type can be safely used in FindIndex (_:_:) function because it guarantees that the equality operator is supported. To illustrate this fact, when you define a function, you can define a equatable type constraint as part of the definition of the type parameter:
Func findindex<t:equatable> (array: [T], _ Valuetofind:t)-> Int? {
for (index, value) in Array.enumerate () {
if value = = Valuetofind {return
index
}
} return
Nil
}
FindIndex (_:_:) The only type parameter is written as t:equatable, which means "any type T conforming to the Equatable protocol".
FindIndex (_:_:) The function can now be compiled successfully and can function on any type that conforms to equatable, such as Double or String:
Let Doubleindex = FindIndex ([3.14159, 0.1, 0.25], 9.3)
//Doubleindex type Int, whose value is nil because 9.3 is not in the array let
Stringinde x = FindIndex (["Mike", "Malcolm", "Andrea"], "Andrea")
//StringIndex type Int?, with a value of 2
Association Type
When defining a protocol, it may be useful to declare one or more associated types as part of the protocol definition at some point. The association type provides a placeholder (or alias) for a type in the protocol, and the actual type of the representation is specified when the agreement is accepted. You can specify the association type by associatedtype keyword.
Association Type Practice
The following example defines a Container protocol that defines an association type ItemType:
Protocol Container {
associatedtype ItemType
mutating func append (item:itemtype)
var count:int {get}
Subscript (i:int)-> ItemType {get}
}
The Container protocol defines three features that must be provided by any type (i.e., container) that incorporates the protocol: it must be available through append (_:) method to add a new element to the container. You must be able to get the number of elements in the container through the Count property and return an INT value. You must be able to retrieve each element in the container by indexing the subscript value type Int.
This protocol does not specify how the elements in the container should be stored, and what type the element must be. This protocol specifies only three features that must be provided by any type that complies with the Container protocol. Compliance types can also provide additional functionality when these three conditions are met.
Any type that complies with the Container protocol must be able to specify the type of elements it stores, and must ensure that only elements of the correct type can be added to the container, and that the type of the element returned through its subscript must be explicitly specified.
To define these three conditions, the Container protocol needs to refer to this type without knowing the specific types of elements in the container. The Container protocol needs to specify any pass append (_:) The elements in the container that the method is added to are the same type, and the type of the element returned through the container subscript is also this type.
To achieve this goal, the Container Protocol declares an association type ItemType, writing Associatedtype ItemType. This protocol cannot define what type of Alias ItemType is, and this information will be left to the type of compliance Agreement provided. Nevertheless, the ItemType alias provides a way to refer to the type of elements in Container and use them for append (_:) Method and subscript to ensure that any Container behavior can be executed as expected.
The following is the previous Non-generic IntStack type, which incorporates and complies with the Container protocol:
struct Intstack:container {
//IntStack's original implementation part
var items = [Int] ()
mutating func push (item:int) {
items . Append (item)
}
mutating func pop ()-> Int {return
items.removelast ()
}
//Container implementation portion of protocol
Typealias ItemType = Int
mutating func append (item:int) {
Self.push (item)
}
var count:int { C14/>return items.count
}
Subscript (i:int)-> Int {return
items[i]
}
}
The IntStack structure implements the three requirements of the Container protocol, and its original function does not conflict with these requirements.
In addition, when implementing Container requirements, IntStack specifies that ItemType is an int type, that is, typealias ItemType = Int, which converts the abstract Container type in the ItemType protocol to a specific int Type.
Because of Swift's type inference, you actually do not have to declare ItemType as Int in the definition of intstack. Because IntStack complies with all requirements of the Container protocol, Swift simply passes append (_:) method, the type of the item parameter and the return value of the subscript can infer the specific type of the ItemType. In fact, if you delete the Typealias ItemType = Int line in the above code, everything still works because Swift knows exactly what type of ItemType should be.
You can also allow the generic Stack structure to comply with the Container protocol:
struct Stack<element>: Container {
//stack<element> Original implementation part
var items = [Element] ()
mutating Func push (item:element) {
items.append (item)
}
mutating func pop ()-> Element {
return
implementation portion of Items.removelast ()}
//Container protocol
mutating func append (item:element) {
Self.push (item)
}
var count:int {return
items.count
}
Subscript (i:int)-> Element {return
items[i]
}
}
This time, the placeholder type parameter Element is used as append (_:) The item parameter of the method and the underlying object return type. Swift can infer from this that the type of Element is the type of ItemType.
to specify an association type by extending a type that exists
Extended Add protocol consistency describes how to use extensions to allow an existing type to conform to a protocol, including the use of an association type.
Swift's Array type already provides append (_:) Method, a Count property, and a subscript that accepts an INT type index value to retrieve its elements. These three features meet the requirements of the Container protocol, which means that you simply declare that the array accepts the protocol to extend the array so that it complies with the Container protocol. You can do this through an empty extension, as described in the adoption Agreement:
Extension Array:container {}
Like the generic Stack structure above, the append of the Array (_:) Methods and subscripts ensure that Swift can infer the type of ItemType. Once you have defined this extension, you can use any Array as a Container.
Where clause
Type constraints allow you to define some coercion requirements for the type parameters of a generic function or generic type.
Defining constraints for an association type is also useful. You can define a constraint for an association type by using the WHERE clause in the argument list. You can require an association type to conform to a particular protocol through the WHERE clause, and a particular type parameter and association type must be of the same type. You can define a WHERE clause, followed by one or more constraints on the associated type, and an equality relationship between one or more type parameters and associated types, by following the WHERE keyword immediately after the type parameter list. You can add a WHERE clause before a function body or a type's curly brace.
The following example defines a generic function called Allitemsmatch to check whether two Container instances contain the same elements in the same order. Returns true if all elements can match, otherwise returns false.
The two Container examined may not be of the same type (although they can be the same), but they must have elements of the same type. This requirement is represented by a type constraint and a WHERE clause:
Func Allitemsmatch<c1:container, C2:container>
(_ Somecontainer:c1, _ Anothercontainer:c2)-> Bool
W Here C1. Item