Type Parameters
In the swapval(_:_:) example above, the placeholder type T is an example of a type parameter. Type parameters are written directly after the function name, between a pair of matching angle brackets (such as T>) and provide and name a placeholder type.
You can use a type parameter to describe the type of a function's parameters (for example, the a and b parameters of the swapval(_:_:) function), the function's return type, or a type annotation within the function's body. When the function is called, the type parameter is replaced with an actual type in each case.
You can give numerous type parameters by using commas to separate various type parameter names within the angle brackets.
Naming the Type Parameters
In most situations, type parameters have descriptive names, such as Key and Value in DictionaryKey, Value> and Element in ArrayElement>, which inform the reader about the type parameter's relationship to the generic type or function used. When there is no meaningful relationship between them, single letters such as T, U, and V, such as T in the swapval(_:_:) function above, are commonly used.
Generic Types
Swift allows you to create your own generic types in addition to generic functions. These are the custom classes, structures, and enumerations that, like Array and Dictionary, can work with any type.
This section demonstrates how to write a generic collection type called Stack. A stack is an ordered set of items that functions similarly to an array but with fewer operations than Swift's Array type. An array enables the addition and removal of elements at any point in the array. On the other hand, a stack only permits new items to be added to the collection's end (known as pushing a new value onto the stack). Similarly, a stack only allows items to be removed from the collection's end (known as popping a value off the stack).
struct IntStack {
var items: [Int] = []
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
The values in the stack are stored in this structure using an Array attribute called items. To push and pop the values on and off the stack, Stack provides two methods: push and pop. Because they must modify (or mutate) the structure's items array, these methods are marked as mutating.
The IntStack type, on the other hand, can only be used with Int values. A generic Stack structure that can manage the stack of any sort of value would be far more useful.
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 how the generic form of Stack is virtually the same as the nongeneric version, except that instead of an actual type of Int, it has a type parameter named Element. This type of parameter is written after the structure's name in a pair of angle brackets (<Element>).
The element specifies a name for a type that will be provided later. Throughout the structure's definition, this future type can be referred to as an Element.
You can create a new Stack instance by writing the name of the type to be stored in the provided stack within the angle brackets. For example look at the below piece of code, to create a new stack of strings, you write Stack<String>():
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// this generic stack now contains a total of 4 strings
Extending a Generic Type
You don't include a type parameter list in the extension specification when you extend a generic type. Instead, the original type parameter list is available within the extension's body, and the original type parameter names are used to refer to the type parameters from the original definition.
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
The above example adds a read-only computed property called topItem to the generic Stack type, which returns the top element of the given stack without removing/popping it.
The topItem property returns an Element-type value that is optional. TopItem returns nil if the stack is empty; if the stack isn't empty, topItem returns the last item in the items array.
This extension does not include a list of type parameters. Instead, the extension uses the Element type parameter name from the Stack type to specify the optional type of the topItem computed property.
Type Constraints
The swapval( : :) function and the Stack type can work with any type. However, enforcing type limits on the kinds that can be used with generic functions and generic types is occasionally useful. According to type constraints, a type parameter must inherit from a specified class or comply with a given protocol or protocol composition.
You can define your own type of constraints when creating a generic custom type, and these constraints provide much of the power and features of the generic programming. Abstract concepts, like Hashable characterise types in terms of their conceptual characteristics rather than their concrete type.
Syntax:
func randomFunc<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body
}
Associated Types
When defining a protocol, it's sometimes helpful to declare one or more associated types as a part of the protocol's definition. An associated type gives a placeholder name to a type used as part of the protocol. The actual type to use for that particular associated type isn't specified until the protocol is totally adopted. Associated types are specified within the associatedtype keyword.
example of a protocol called Container, which declares an associated type called Item:
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
Any container must have three capabilities, according to the Container protocol:
- An append(_:) method must be able to add a new item to the container.
- A count property, that will return an Int value must be available to get a count of the elements in the container.
- Each item in the container must be retrievable using a subscript that takes an Int index value.
This protocol makes no suggestions for how the things in the container should be stored or what types they can be. The protocol only describes the three bits of functionality that any type must supply to be classified as a Container. As long as these three requirements are satisfied, a conforming type can provide additional capabilities.
Generic Where Clauses
Type constraints, as defined in Type Constraints, let you specify criteria for the type parameters of a generic function, subscript, or type.
It's also a good idea to define requirements for associated forms. This is accomplished by creating a generic where clause. You can use a generic where clause to specify that an associated type must follow a specific protocol or those particular type parameters and associated types must match. The where keyword is followed by constraints for associated types or equality relationships between types and associated types in a generic where clause. Before the opening of a curly brace of a type or a function's body, you write a generic where clause
func checkMatching<C1: Container, C2: Container>
(_ firstContainer: C1, _ secondContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
// Check that both the containers contain the same number of items or Not.
if firstContainer.count != secondContainer.count {
return false
}
// Check each fo the pair for items to see if they're equivalent.
for i in 0..<firstContainer.count {
if firstContainer[i] != secondContainer[i] {
return false
}
}
// if All items match, so return true.
return true
}
The previous example introduces a generic function named allItemsMatch, which determines whether two Container instances have the same elements in the same order. If all elements match, the function returns true; if they don't, the method returns false.
Generic Subscripts
Subscripts can include generic where clauses and can be generic. After subscript, you write the placeholder type name inside angle brackets, and directly before the opening curly brace of the subscript's body, you write a generic where clause.
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
}
}
I hope you have got a fair idea of how the generics are implemented in Swift. Let's move to the FAQs.
Frequently Asked Questions
What are Swift generics?
Swift 4 has 'Generic' features that allow you to construct reusable and flexible functions and types. To avoid repetition and offer abstraction, generics are utilized. Generics code is used to create Swift 4 standard libraries.
What is the difference between any Swift and generic?
Generics and Any are frequently used for similar reasons, although their behavior is somewhat different. Generics are statically checked at build time, whereas Any and runtime programming is commonly used in languages lacking generics.
Define protocols in Swift?
A protocol is a set of methods or attributes that can be used by classes to implement (or any other types). The protocol keyword is used to define a protocol.
Define multithreading in Swift?
Multithreading can be defined as the process which facilitates the CPU to create and execute concurrent threads.
Conclusion
In this article, we have extensively discussed Swift generics and how different types of clauses and rules are used to improve the functionality of the Swift Generic Functions.
To read more about Swift, you can refer here, Swift, Why swift?, Archives.
Refer to our guided paths on Coding Ninjas Studio to learn more about DSA, Competitive Programming, JavaScript, System Design, etc. Enrol in our courses, refer to the mock test and problems; look at the interview experiences and interview bundle for placement preparations.
Do upvote our blog to help other ninjas grow and Learn.
Happy Learning!