Swift - Generics



Swift provides a special feature known as ‘Generic' to write flexible and reusable functions and types that can work well with any other type. Generics are used to avoid duplication and to provide abstraction. Swift has some standard libraries that are built with generics code. Arrays and dictionary types belong to a generic collection.

So, we are allowed to create an array that holds a String value or can able to create an array that holds an int value. Similarly, for the dictionary.

Example

func exchange(inout a: Int, inout b: Int) {
   let temp = a
   a = b
   b = temp
}

var numb1 = 100
var numb2 = 200

print("Before Swapping values are: \(numb1) and \(numb2)")
exchange(&numb1, &numb2)
print("After Swapping values are: \(numb1) and \(numb2)")

Output

It will produce the following output −

Before Swapping values are: 100 and 200
After Swapping values are: 200 and 100

Generic Functions

Generic functions are special functions that can be used to access any data type like 'Int' or ‘String’ while maintaining the type safety. Or we can say that generic functions can work with different types without specifying the actual type at the time of declaration.

Example

In the following example, The function exchange() is used to swap values described in the above program and <T> is used as a type parameter. For the first time, function exchange() is called to return 'Int' values and the second call to the function exchange() will return 'String' values. Multiple parameter types can be included inside the angle brackets separated by commas.

func exchange<T>(_ a: inout T, _ b: inout T) {
   let temp = a
   a = b
   b = temp
}

var numb1 = 100
var numb2 = 200

print("Before Swapping Int values are: \(numb1) and \(numb2)")
exchange(&numb1, &numb2)
print("After Swapping Int values are: \(numb1) and \(numb2)")

var str1 = "Generics"
var str2 = "Functions"

print("Before Swapping String values are: \(str1) and \(str2)")
exchange(&str1, &str2)
print("After Swapping String values are: \(str1) and \(str2)")

Output

It will produce the following output −

Before Swapping Int values are: 100 and 200
After Swapping Int values are: 200 and 100
Before Swapping String values are: Generics and Functions
After Swapping String values are: Functions and Generics

Type Parameters

Type parameters are named as user-defined to know the purpose of the type parameter that it holds. Swift provides <T> as a generic type parameter name. However type parameters like Arrays and Dictionaries can also be named as key, values to identify that they belong to the type ‘Dictionary'. We are allowed to provide more than one type parameter by writing multiple type parameter names in the angle brackets, where each name is separated by a comma.

Example

// Generic Types
struct TOS<T> {
   var items = [T]()
    
   mutating func push(item: T) {
      items.append(item)
   }
    
   mutating func pop() -> T? {
      return items.popLast()
   }
}

var tos = TOS<String>()
tos.push(item: "Swift 4")
print(tos.items)

tos.push(item: "Generics")
print(tos.items)

tos.push(item: "Type Parameters")
print(tos.items)

tos.push(item: "Naming Type Parameters")
print(tos.items)

if let deletetos = tos.pop() {
   print("Popped item: \(deletetos)")
} else {
   print("The stack is empty.")
}
Output

It will produce the following output −

["Swift 4"]
["Swift 4", "Generics"]
["Swift 4", "Generics", "Type Parameters"]
["Swift 4", "Generics", "Type Parameters", "Naming Type Parameters"]
Popped item: Naming Type Parameters

Generic Type and Extending a Generic Type

In Swift, we are allowed to define generic types to create flexible and reusable structures, classes or enumerations that can easily work with any data type. Also, we are allowed to extend the functionality of generic types using the extension keyword.

Example

struct TOS<T> {
   var items = [T]()
   mutating func push(item: T) {
      items.append(item)
   }

   mutating func pop() -> T {
      return items.removeLast()
   }
}

var tos = TOS<String>()
tos.push(item: "Swift 4")
print(tos.items)

tos.push(item: "Generics")
print(tos.items)

tos.push(item: "Type Parameters")
print(tos.items)

tos.push(item: "Naming Type Parameters")
print(tos.items)

extension TOS {
   var first: T? {
      return items.isEmpty ? nil : items[items.count - 1]
   }
}

if let first = tos.first {
   print("The top item on the stack is \(first).")
}

Output

It will produce the following output −

["Swift 4"]
["Swift 4", "Generics"]
["Swift 4", "Generics", "Type Parameters"]
["Swift 4", "Generics", "Type Parameters", "Naming Type Parameters"]
The top item on the stack is Naming Type Parameters.

Type Constraints

Swift allows 'type constraints' to specify whether the type parameter inherits from a specific class, or to ensure protocol conformance standard. We are allowed to use them with classes and protocols to specify more complex requirements. While creating custom generic types we are allowed to create our own type constraints.

Syntax

Following is the syntax for the type constraints −

Func functionName<T: className, U: protocolName>(variable1: T, variable2: U){
   // Function body
}

Example

// A generic function with a type constraint
func show<T: CustomStringConvertible>(item: T) {
   print(item.description)
}

let str = "Welcome Swift"
let number = 22

show(item: str) 
show(item: number)    

Output

It will produce the following output −

Welcome Swift
22

Where Clauses

Type constraints enable the user to define requirements on the type parameters associated with a generic function or type. For defining requirements for associated types 'where' clauses are declared as part of the type parameter list. 'where' keyword is placed immediately after the list of type parameters followed by constraints of associated types, and equality relationships between types and associated types.

Example

protocol Container {
   typealias ItemType
   mutating func append(item: ItemType)
   var count: Int { get }
   subscript(i: Int) -> ItemType { get }
}

struct Stack<T>: Container {
   
   // original Stack<T> implementation
   var items = [T]()
   mutating func push(item: T) {
      items.append(item)
   }
   mutating func pop() -> T {
      return items.removeLast()
   }

   // conformance to the Container protocol
   mutating func append(item: T) {
      self.push(item)
   }
   var count: Int {
      return items.count
   }
   subscript(i: Int) -> T {
      return items[i]
   }
}

func allItemsMatch<
   C1: Container, C2: Container
   where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
   (someContainer: C1, anotherContainer: C2) -> Bool {
   
      // 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..<someContainer.count {
      if someContainer[i] != anotherContainer[i] {
         return false
      }
   }
   // all items match, so return true
   return true
}

var tos = Stack<String>()
tos.push("Swift 4")
print(tos.items)

tos.push("Generics")
print(tos.items)

tos.push("Where Clause")
print(tos.items)

var eos = ["Swift 4", "Generics", "Where Clause"]
print(eos)

Output

It will produce the following output −

[Swift 4]
[Swift 4, Generics]
[Swift 4, Generics, Where Clause]
[Swift 4, Generics, Where Clause]
Advertisements