In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
Today, I will talk to you about the generic features of Swift, which may not be well understood by many people. in order to make you understand better, the editor has summarized the following content for you. I hope you can get something according to this article.
Generic code allows you to write flexible, reusable functions that can be used for any type of function according to the requirements you define. You can write code that is reusable, clearly expressed, and abstract.
Generics is one of the most powerful features of Swift, and many Swift standard libraries are built on generic code. In fact, you don't even realize that generics are used all the time in language guides. For example, the Array and Dictionary types of Swift are generic collections.
You can create an array that holds Int values, or an array that holds String values, or even any other type of array that Swift can create. Similarly, you can create a dictionary that stores values of any specified type, and there are no restrictions on the type.
two。 Problems solved by generics
The following swapTwoInts (_: _:) is a standard non-generic function that exchanges two Int values:
Func swapTwoInts (_ a: inout Int, _ b: inout Int) {let temporaryA = a = b = temporaryA}
As described in the input and output formal parameters, this function exchanges the values of an and b with the input and output formal parameters.
The swapTwoInts (_: _:) function gives the original value of b to an and 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 = 107swapTwoInts (& someInt, & anotherInt) print ("someInt is now\ (someInt), and anotherInt is now\ (anotherInt)") / / Prints "someInt is now 107, and anotherInt is now 3"
The swapTwoInts (_: _:) function is useful, but it can only be used for Int values. If you want to exchange two String values, or two Double values, you can only write more functions, such as the following swapTwoStrings (_:) and swapTwoDoubles (_: _:) functions:
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 = b = temporaryA}
You may have noticed that the bodies of swapTwoInts (_: _:), swapTwoStrings (_: _:), and swapTwoDoubles (_: _:) functions are the same. The only difference is that they receive different types of values (Int, String, and Double).
It is more practical and flexible to write a function that can exchange values of any type. Generic code allows you to write functions like this. (generic versions of these functions are defined below.)
Of the three functions, it is important that an and b are defined as the same type. If the types an and b are not the same, their values cannot be exchanged. Swift is a type-safe language that does not allow (for example) a variable of type String and a variable of type Double to exchange values. An attempt to do so will cause a compilation error.
3. Generic function
Generic functions can be used for any type. Here is the generic version of the swapTwoInts (_: _:) function mentioned above, called swapTwoValues (_: _:):
Func swapTwoValues (_ a: inout T, _ b: inout T) {let temporaryA = a = b = temporaryA}
The body of the swapTwoValues (_: _:) and swapTwoInts (_: _:) functions is the same. However, the first line of swapTwoValues (_: _:) is a little different from that of swapTwoInts (_: _:). Here is a comparison of the first line:
Func swapTwoInts (_ a: inout Int, _ b: inout Int) func swapTwoValues (_ a: inout T, _ b: inout T)
The generic version of the function uses a placeholder type name (called T here) instead of an actual type name (such as Int, String, or Double). The placeholder type name does not state what T must be, but it does say that an and b must both be of the same type T, or the type represented by T. The type actually used by substitute T will be determined each time the swapTwoValues (_: _:) function is called.
The other difference is that the generic function name (swapTwoValues (_: _:)) is followed by the placeholder type name (T) wrapped in angle brackets (). Angle brackets tell Swift that T is the placeholder type name in a swapTwoValues (_: _:) function definition. Because T is a placeholder, Swift does not look for a type that is really called T.
Now you can call the swapTwoValues (_: _:) function by calling swapTwoInts, and in addition, you can pass two values of any type to the function, as long as the two arguments are of the same type. Each time swapTwoValues (_: _:) is called, the type used for T is automatically inferred based on the value type of the function passed in.
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"
The swapTwoValues (_: _:) function defined above is inspired by a generic function called swap. The swap function is part of the Swift standard library and can be used in your application. If you need to use the function of the swapTwoValues (_: _:) function in your own code, you can use the swap (_: _:) function provided by Swift directly. You don't need to implement it yourself.
4. Type form parameter
In swapTwoValues (_: _:) above, the placeholder type T is an example of a type formal parameter. The type form parameter specifies and names a placeholder type, next to a pair of angle brackets written after the function name (for example).
Once you have specified a type formal parameter, you can use it to define the type of a function formal parameter (such as the formal parameters an and b in the swapTwoValues (_: _:) function, or to use it as the function return value type, or to mark the type in the function body. In different cases, the type form parameter is replaced with the actual type when the function is called. (in the swapTwoValues (_: _:) example above, T is replaced with Int the first time the function is called, and String is replaced the second call.)
You can provide more type-form parameters by writing multiple type-form parameter names separated by commas in angle brackets.
5. Named type form parameter
In most cases, the name of the type form parameter should be descriptive, such as Dictionary
Type form parameters are always named with uppercase hump nomenclature (such as T and MyTypeParameter) to indicate that they are placeholders of a type, not a value.
6. Generic type
In addition to generic functions, Swift allows you to define your own generic types. They can be used for any type of custom classes, structures, and enumerations, similar to Array and Dictionary.
This chapter will show you how to write a generic collection type called Stack. A stack is an ordered collection of values, similar to an array, but with stricter operational restrictions than the Array type of Swift. The array allows you to insert and remove elements anywhere in it. However, new elements of the stack can only be added to the end of the collection (this is called stack pressing). Similarly, the stack only allows elements to be removed from the end of the collection (this is called off-stack).
The idea that the UINavigationController class manages the view controller in its navigation hierarchy is the stack used. You can add (or push) a view controller to the navigation stack by calling the pushViewController (_: animated:) method of the UINavigationController class, and remove (or pop) a view controller from the navigation stack with the popViewControllerAnimated (_:) method. Stack is a useful collection model when you need to manage a collection in a strict "last-in, first-out" way.
The following illustration shows the behavior of stacking and unstacking:
Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community
Now there are three values in the stack.
The fourth value is pressed to the top of the stack.
There are now four values in the stack, with the most recently added at the top
The element at the top of the stack is removed, or "off the stack".
After removing one element, there are three more elements in the stack.
Here's how to write a non-generic version of the stack, which is a stack of Int values:
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 attribute called items to store the values in the stack. Stack provides two methods, push and pop, to add and remove values from the stack. These two methods are marked mutating because they need to modify (or change) the items array of the structure.
The IntStack type shown above can only be used for Int values. But it is more practical to define a generic Stack so that you can manage the stack of values of any type.
Here is a generic version of the same code:
Struct Stack {var items = [Element] () mutating func push (_ item: Element) {items.append (item)} mutating func pop ()-> Element {return items.removeLast ()}}
Note that this generic Stack is essentially the same as the non-generic version, except that the actual Int type is replaced with a type form parameter called Element. This type form parameter is written in a pair of angle brackets (), immediately after the structure name.
Element defines a placeholder name for "some type of Element" that will be provided later. This future type can be referenced by "Element" anywhere within the structure definition. In this example, there are three places where Element is used as a placeholder:
Create a property named items and initialize it with an empty array of values of type Element
Specifies that the push (_:) method has a formal parameter called item, which must be of type Element
Specifies that the return value of the pop () method is a value of type Element.
Because it is generic, you can use Stack to create any type of stack that is valid in Swift in a manner similar to Array and Dictionary.
Create a new instance of Stack by writing out the types stored in the stack in angle brackets. For example, to create a new string stack, you can write Stack ():
Var stackOfStrings = Stack () stackOfStrings.push ("uno") stackOfStrings.push ("dos") stackOfStrings.push ("tres") stackOfStrings.push ("cuatro") / / the stack now contains 4 strings
This is the diagram of stackOfStrings after pressing four values into the stack:
Remove from the stack and return the top value, "cuatro":
Let fromTheTop = stackOfStrings.pop () / / fromTheTop is equal to "cuatro", and the stack now contains 3 strings
This is the stack icon after the values at the top of the stack come out of the stack:
7. Extend a generic type
When you extend a generic type, you do not need to provide a list of type formal parameters in the extended definition. The type form parameter list of the original type definition is still valid in the extension, and the original type form parameter list name is also used to extend the type form parameter.
The following example extends the generic Stack type by adding a read-only evaluation property called topItem that returns the top element 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 of type Element. TopItem returns nil if the stack is empty, and topItem returns the last element of the items array if the stack is not empty.
Note that this extension does not define a formal parameter list of types. Instead, the extension uses Stack's existing type form parameter name, Element, to indicate the optional type of the calculated property topItem.
Now, without removing the element, you can use the topItem calculation property of any Stack instance to access and query the element at its top:
If let topItem = stackOfStrings.topItem {print ("The top item on the stack is\ (topItem).")} / / Prints "The top item on the stack is tres." 8. Type constraint
The swapTwoValues (_: _:) function and the Stack type can be used for any type. However, it is sometimes useful to force types and generic types used for generic functions to follow specific type constraints. A type constraint indicates that a type formal parameter must inherit from a specific class or follow a specific protocol or combination protocol.
For example, the Dictionary type of Swift sets a limit on the types that can be used for keys in the dictionary. As described in the dictionary, the type of dictionary key must be hashable. That is, it must provide a way for it to be uniquely represented. Dictionary needs its key to be hashable so that it can check whether the dictionary contains the value of a particular key. Without this requirement, Dictionary cannot tell whether to insert or replace the value of a specified key, nor can it look up the value of a given key in the dictionary.
This requirement is achieved through a type constraint on the Dictionary key type, which indicates that the key type must follow the Hashable protocol defined in the Swift standard library. All Swift basic types (such as String, Int, Double, and Bool) are hashable by default.
When creating custom generic types, you can define your own type constraints that provide powerful generic programming capabilities. Abstract concepts like Hashable represent types based on conceptual characteristics rather than exact types.
▐ 8.1 type constraint syntax
Place a class or protocol after a type formal parameter name as part of the formal parameter list, separated by a colon to write a type constraint. The following shows the basic syntax of a generic function type constraint (the same as the syntax of a generic type):
Func someFunction (someT: t, someU: U) {/ / function body goes here}
The above hypothetical function has two formal arguments. The first type form parameter, T, has a type constraint that requires T to be a subclass of SomeClass. The second type form parameter, U, has a type constraint that requires U to follow the SomeProtocol protocol.
The Application of ▐ 8.2 Type constraint
This is a non-generic function called findIndex (ofString:in:) that looks for a given String value in a given array of String values. The findIndex (ofString:in:) function returns an optional Int value, and if a given string is found, it returns the index value of the first matching string in the array, or nil if the given string is not found:
Func findIndex (ofString valueToFind: String, in array: [String])-> Int? {for (index, value) in array.enumerated () {if value = = valueToFind {return index}} return nil}
The findIndex (ofString:in:) function can be used to find string values in a string array:
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"
The principle of finding the index of a value in an array can only be used for strings. However, by replacing all the strings used with a T-type value, you can write the same function with a generic function.
A function called findIndex (of:in:) is written here, which may be a generic version of the findIndex (ofString:in:) function you expect. Note that the return value of the function is still Int? because the function returns an optional index number rather than an optional value in the array. This function is not compiled, which is explained later in the example:
Func findIndex (of valueToFind: t, in array: [T])-> Int? {for (index, value) in array.enumerated () {if value = = valueToFind {return index}} return nil}
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 equality operator (= =). If you create your own class or structure to describe a complex data model, for example, for that class or structure, the meaning of "equal" cannot be guessed for you by Swift. Therefore, there is no guarantee that this code can be used for all types that T can represent, and you will be prompted with a corresponding error when you try to compile the code.
In short, the Swift standard library defines a protocol called Equatable, which requires that types that follow its protocol implement the equality operator (= =) and the unequal operator (! =) to compare any two values of that type. All types in the Swift standard library automatically support the Equatable protocol.
Any type of Equatable can be safely used with the findIndex (of:in:) function because it is guaranteed that those types support the equality operator. To express this fact, when you define a function, write the Equatable type constraint as part of the type form parameter definition:
Func findIndex (of valueToFind: t, in array: [T])-> Int? {for (index, value) in array.enumerated () {if value = = valueToFind {return index}} return nil}
The type form parameter of findIndex (of:in:) is written as T: Equatable, which means "any type T that follows the Equatable protocol".
The findIndex (of:in:) function can now be compiled successfully and can be used for any type of Equatable, 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 is not in the array let stringIndex = findIndex (of: "Andrea", in: ["Mike", "Malcolm", "Andrea"]) / / stringIndex is an optional Int containing a value of 29. Association type
When defining a protocol, it is sometimes useful to declare one or more association types in the protocol definition. The association type gives a placeholder name to the type used in the protocol. The actual type used for this association type is not specified until the protocol is adopted. The association type is specified by the associatedtype keyword.
Application of Association types in ▐ 9.1
Here is an example protocol called Container that declares an association type called ItemType:
Protocol Container {associatedtype ItemType mutating func append (_ item: ItemType) var count: Int {get} subscript (I: Int)-> ItemType {get}}
The Container protocol defines three functions that all containers must provide:
Must be able to add new elements to the container through the append (_:) method
You must be able to get the number of elements in the container through a count attribute that returns an Int value
Each element in the container must be able to be fetched through the subscript of the Int index value.
This protocol does not specify how elements are stored in the container, nor does it specify the types of elements that are allowed to be stored in the container. The protocol specifies only three functions that must be provided in order to be a Container. The type that follows the protocol can provide other functions, as long as these three requirements are met.
Any type that follows the Container protocol must be able to specify the type of value it stores. In particular, it must ensure that only elements of the correct type can be added to the container, and that the element type returned by the subscript of that type must be correct.
To define these requirements, the Container protocol needs a way to reference the type of elements that the container will store without knowing the specific type of the container. The Container protocol needs to specify that all values passed to the append (_:) method must be of the same value type as the element in the container, and that the value returned by the container subscript is the same as the value type of the element in the container.
To meet these requirements, the Container protocol declares an association type called ItemType, which is written as associatedtype ItemType. The protocol does not define what type of ItemType is, and this information is left to the type that follows the protocol. However, the alias ItemType, which provides a way to reference element types in Container, defines a type for Container methods and subscripts, ensuring that any behavior expected by Container is met.
This is the previous non-generic version of IntStack to follow 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 ItemType = Int mutating func append (_ item: Int) {self.push (item)} Var count: Int {return items.count} subscript (I: Int)-> Int {return items [I]}}
IntStack implements all the requirements of the Container protocol. In order to meet these requirements, it encapsulates the existing methods in IntStack.
In addition, in order to implement the Container protocol, IntStack specifies that the type applicable to ItemType is the Int type. Typealias ItemType = Int converts ItemType abstract types to concrete Int types.
Thanks to Swift's type inference function, you don't really have to declare a specific Int type ItemType in the IntStack definition. Because IntStack complies with all the requirements of the Container protocol, Swift can infer the appropriate ItemType by simply looking at the item form parameter of the append (_:) method and the return type of the subscript. If you do remove typealias ItemType = Int from the above code, everything will work fine, because the type of ItemType is very clear.
You can also make a generic Stack type that follows the Container protocol:
Struct Stack: Container {/ / original Stack 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]}}
This time, the type form parameter Element is used for the item form parameter of the append (_:) method and the return type of the subscript. Therefore, for this container, Swift can infer that Element is the type that applies to ItemType.
▐ 9.2 add constraints to association types
You can add a constraint to the association type in the protocol to require the type to follow to meet the constraint. For example, the following code defines a version of Container that requires that the elements in the container are decidable and so on.
Protocol Container {associatedtype Item: Equatable mutating func append (_ item: Item) var count: Int {get} subscript (I: Int)-> Item {get}}
To follow this version of Container, the container's Item must follow the Equatable protocol.
▐ 9.3 uses protocols in association type constraints
The agreement can emerge as its own requirement. For example, there is a protocol that refines the Container protocol and adds a suffix (_:) method. The suffix (_:) method returns a given number of elements in the container from back to front, storing them in an instance of type Suffix.
Protocol SuffixableContainer: Container {associatedtype Suffix: SuffixableContainer where Suffix.Item = = Item func suffix (_ size: Int)-> Suffix}
In this protocol, Suffix is an association type, just like the Item type of Container in the above example. Suffix has two constraints: it must follow the SuffixableContainer protocol (that is, the protocol currently defined) and its Item type must be the same as the Item type in the container. The constraint for Item is a where clause, which is discussed in the following extension with a generic Where clause.
Here is an extension of the Stack type from the loop strong reference of the closure, which adds compliance to the SuffixableContainer protocol:
Extension Stack: SuffixableContainer {func suffix (_ size: Int)-> Stack {var result = Stack () for index in (count-size).. Stack {var result = Stack () for index in (count-size).. Bool where C1.ItemType = = C2.ItemType, C1.ItemType: 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 are equivalent. For i in 0.. Bool {guard let topItem = items.last else {return false} return topItem = = item}}
This new isTop (_:) method first verifies that the stack is not empty, and then compares the given element with the top element of the stack. If you try to do this without using generic where clauses, you may encounter a problem: the implementation of isTop (_:) uses the = = operator, but the definition of Stack does not require its elements to be equal, so using the = = operator will result in a run-time error. Using generic where clauses allows you to add a new requirement to the extension, so that the extension will only add the isTop (_:) method to the stack when the elements in the stack are decidable, etc.
This is the usage:
If stackOfStrings.isTop ("tres") {print ("Top element is tres.")} else {print ("Top element is something else.")} / / Prints "Top element is tres."
If you try to call the isTop (_:) method on a stack where elements cannot be judged, you will get a runtime error.
Struct NotEquatable {} var notEquatableStack = Stack () let notEquatableValue = NotEquatable () notEquatableStack.push (notEquatableValue) notEquatableStack.isTop (notEquatableValue) / / Error
You can use generic where clauses to extend to a protocol. The following chestnut adds a startsWith (_:) method to the previous Container protocol extension.
Extension Container where Item: Equatable {func startsWith (_ item: Item)-> Bool {return count > = 1 & & self [0] = = item}}
The startsWith (_:) method first ensures that the container has at least one element, and then it checks whether the first element is the same as the given element. This new startsWith (_:) method can be applied to any type that follows the Container protocol, including the stack and array we used earlier, as long as the elements of the container can be determined, and so on.
If [9,9,9] .startsWith (42) {print ("Starts with 42.")} else {print ("Starts with something else.")} / / Prints "Starts with something else."
The generic where clause in the above example requires Item to follow the protocol, but you can also write a generic where clause to require Item to be of a specific type. For example:
Extension Container where Item = = Double {func average ()-> Double {var sum = 0.0 for index in 0.. Double where Item = = Int {var sum = 0.0 for index in 0.. 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.endsWith (37)) / / Prints "true"
This example adds an average () method to Container when the element is an integer, and an endsWith (_:) method if the element is decidable, etc. Both functions contain the generic where clause, which adds type restrictions to the formal parameter Item type that the paradigm originally declared in Container.
If you don't want to use contextual where clauses, you need to write two extensions, each using a generic where clause. The following example has the same effect as the above example.
Extension Container where Item = = Int {func average ()-> Double {var sum = 0.0 for index in 0.. Bool {return count > = 1 & & self [count-1] = = item}}
The context where clause is used, and both average () and endsWith (_:) uninstall the same extension, because the generic where clause of each method declares the premise that it needs to be valid. Moving these requirements to an extended paradigm where clause allows the method to take effect in the same situation, but this requires an extension to correspond to a requirement.
13. Generic Where clauses of associated types
You can include a generic where clause in the association type. For example, suppose you want to make a Container that contains traversers, such as the Sequence protocol in the standard library. Then you would write like this:
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}
The generic where clause in Iterator requires the iterator to traverse all the elements in the container with the same type, regardless of the type of the iterator. The makeIterator () function provides access to the container's traversal.
For a protocol that inherits from other protocols, you can qualify the association type in the inherited protocol by including a generic where clause in the protocol declaration. For example, the following code declares a ComparableContainer protocol that requires Item to comply with Comparable:
Protocol ComparableContainer: Container where Item: Comparable {} 14. Generic subscript
Subscripts can be generic, and they can contain generic where clauses. You can write type placeholders in angle brackets after subscript, and you can write generic where clauses in front of subscript code blocks. For example:
Extension Container {subscript (indices: Indices)-> [Item] where Indices.Iterator.Element = = Int {var result = [Item] () for index in indices {result.append} return result}}
This extension of the Container protocol adds an array that receives a series of indexes and returns an array containing a given index element. The generic subscript is defined as follows:
The generic form parameter Indices in angle brackets must be a type that follows the Sequence protocol in the standard library
The subscript receives a single formal parameter, indices, which is an instance of type Indices
The generic where clause requires that the traversal of the sequence must traverse elements of type Int. This ensures that the indexes in the sequence are of the same type as the container index.
Taken together, these qualifications mean that the passed-in indices form parameter is a sequence of integers.
After reading the above, do you have any further understanding of the generic features of Swift? If you want to know more knowledge or related content, please follow the industry information channel, thank you for your support.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.