English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Swift provides generics to allow you to write flexible and reusable functions and types.
Swift's standard library is built using generic code.
Swift's array and dictionary types are part of the generic set.
You can create an Int array, a String array, or even an array of any other Swift type data.
The following example is a non-generic function exchange used to swap two Int values:
// Define a function to swap two variables func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("Before swap data: \(numb1) and \(numb2)") swapTwoInts(&numb1, \&numb2) print("Data after swap: \(numb1) and \(numb2)")
The output result of the above program is as follows:
Data before swap: 100 and 200 Data after swap: 200 and 100
The above example only applies to swapping variables of the Int type. If you want to swap two String values or Double values, you need to write a corresponding function, such as swapTwoStrings(_:_:) and swapTwoDoubles(_:_:), as 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 }
From the above code, it can be seen that the functional code is the same, but the types are different. In this case, we can use generics to avoid writing duplicate code.
Generics use placeholder type names (represented by the letter T here) to replace actual type names (such as Int, String, or Double).
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
swapTwoValues followed by a placeholder type name (T) enclosed in parentheses (<T>
)。These angle brackets tell Swift that T is a placeholder type name within the definition of the swapTwoValues(_:_:) function, so Swift will not look for an actual type named T.
The following example is a generic function exchange used to swap two Int and String values:
// Define a function to swap two variables func swapTwoValues<T>(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("Data before swap: \(numb1) and \(numb2)") swapTwoValues(&numb1, \&numb2) print("Data after swap: \(numb1) and \(numb2)") var str1 = "A" var str2 = "B" print("Data before swap: \(str1) and \(str2)") swapTwoValues(&str1, \&str2) print("Data after swap: \(str1) and \(str2)")
The output result of the above program is as follows:
Data before swap: 100 and 200 Data after swap: 200 and 100 Data before swap: A and B Data after swap: B and A
Swift allows you to define your own generic types.
Custom classes, structures, and enums work with any type, similar to the usage of Array and Dictionary.
Next, let's write a generic collection type named Stack, which only allows new elements to be added at the end of the collection (referred to as pushing), and elements can only be removed from the end (referred to as popping).
The parsing from left to right in the picture is as follows:
There are three values in the stack.
The fourth value is pushed onto the top of the stack.
Now there are four values in the stack, with the most recently pushed value at the top.
The value at the top of the stack is removed, or called popping.
After removing a value, the stack now only has three values.
The following example is a non-generic version of a stack, taking the 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 to store values in the stack. Stack provides two methods: push(_:) and pop(), which are used 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.
The IntStack structure above can only be used for Int type. However, you can define a generic Stack structure to be able to handle values of any type.
Below is the 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() } } var stackOfStrings = Stack<String>() print("String element is pushed onto the stack:") stackOfStrings.push("google") stackOfStrings.push("w3codebox) print(stackOfStrings.items); let deletetos = stackOfStrings.pop() print("Popped element: " + deletetos) var stackOfInts = Stack<Int>() print("Integer elements into the stack: ") stackOfInts.push(1) stackOfInts.push(2) print(stackOfInts.items);
The example execution result is:
String element is pushed onto the stack: ["google", "w3codebox"] Popped element: w3codebox Integer elements into the stack: [1, 2]
The Stack is basically the same as IntStack, and the placeholder type parameter Element replaces the actual Int type.
In the above example, Element is used as a placeholder in the following three places:
Create items Property, using Element An empty array of the type to initialize it.
Specify push(_:) The unique parameter of the method item The type must be Element Type.
Specify pop() The return value type of the method must be Element Type.
When you extend a generic type (using the extension keyword), you do not need to provide a type parameter list in the extension definition. It is more convenient that the type parameter list declared in the original type definition can be used in the extension, and the parameter names from the original type are used as references to the type parameters in the original definition.
The following example extends the generic type Stack, adding a read-only computed property named topItem that will return the current top element of the stack without removing it from the stack:
struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } var stackOfStrings = Stack<String>() print("String element is pushed onto the stack:") stackOfStrings.push("google") stackOfStrings.push("w3codebox) if let topItem = stackOfStrings.topItem { print("The top element of the stack is: \(topItem).") } print(stackOfStrings.items)
In the example, the topItem property will return an optional value of type Element. When the stack is empty, topItem will return nil; when the stack is not empty, topItem will return the last element of the items array.
The output result of the above program is as follows:
String element is pushed onto the stack: The top element of the stack is: w3codebox. ["google", "w3codebox"]
We can also specify associated types by extending an existing type.
For example, Swift's Array type already provides an append(_:) method, a count property, and an index that accepts an Int type index value to retrieve its elements. These three features all conform to the Container protocol, so you can simply declare Array to adopt the protocol to extend Array.
The following example creates an empty extension:
extension Array: Container {}
Type constraints specify a type parameter that must inherit from a specified class or conform to a specific protocol or protocol composition.
You can write a type constraint after a type parameter name, separated by a colon, as part of the type parameter chain. The basic syntax for type constraints in generic functions (which is the same as the syntax for generic types) is as follows:
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // 这里是泛型函数的函数体部分 }
上面这个函数有两个类型参数。第一个类型参数 T,有一个要求 T 必须是 SomeClass 子类的类型约束;第二个类型参数 U,有一个要求 U 必须符合 SomeProtocol 协议的类型约束。
// 非泛型函数,查找指定字符串在数组中的索引 func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { // 找到返回索引值 return index } } } 3codebox if let foundIndex = findIndex(ofString: "w3codebox print("w3The index of codebox is (foundIndex") }
The index subscripts start from 0.
The output result of the above program is as follows:
w3The index of codebox is 3
Swift uses the associatedtype keyword to set associated type examples.
The following example defines a Container protocol that defines an associated type ItemType.
The Container protocol specifies only three functions that any type that conforms to the Container protocol must provide. Types that conform to the protocol can also provide additional functions when they meet these three conditions.
// Container protocol protocol Container { associatedtype ItemType // Add a new element to the container mutating func append(_ item: ItemType) // Get the number of elements in the container var count: Int { get } // Retrieve each element in the container by index with the type Int subscript(i: Int) -> ItemType { get } } // The Stack structure conforms to the Container protocol struct Stack<Element>: Container { // Original implementation part of Stack<Element> var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } // Part of the implementation of the Container protocol mutating func append(_ item: Element) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Element { return items[i] } } var tos = Stack<String>() tos.push("google") tos.push("w3codebox) tos.push("taobao") // List of elements print(tos.items) // Number of elements print( tos.count)
The output result of the above program is as follows:
["google", "w3["codebox", "taobao"] 3
Type constraints can ensure that types conform to the definition constraints of generic functions or classes.
You can define constraints on parameters using a where clause in the parameter list.
You can write a where clause immediately following the type parameter list, and the where clause is followed by one or more constraints on the associated type, as well as (or) one or more equality relationships between types and associated types.
The following example defines a generic function named allItemsMatch, used to check whether two Container instances contain the same elements in the same order.
If all elements can match, return true, otherwise return false.
// Container protocol protocol Container { associatedtype ItemType // Add a new element to the container mutating func append(_ item: ItemType) // Get the number of elements in the container var count: Int { get } // Retrieve each element in the container by index with the type Int subscript(i: Int) -> ItemType { get } } // // Generic type TOS that follows the Container protocol struct Stack<Element>: Container { // Original implementation part of Stack<Element> var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } // Part of the implementation of the Container protocol mutating func append(_ item: Element) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Element { return items[i] } } // Extension, treating Array as a Container extension Array: Container {} func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable { // Check if two containers contain the same number of elements if someContainer.count != anotherContainer.count { return false } // Check if each pair of elements is equal for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // All elements match, return true return true } var tos = Stack<String>() tos.push("google") tos.push("w3codebox) tos.push("taobao") var aos = ["google", "w3["codebox", "taobao"] if allItemsMatch(tos, aos) { print("Match All Elements") } else { print("Element does not match") }
The output result of the above program is as follows:
Match All Elements