Swift - Generics
Published: 2022-04-06 12:16:09

Generic code enables you to write flexible, reusable(avoids duplication) **functions** and **types** that can work with **any type**, subject to requirements that you define. 

- Array, Set and Dictionary are Generic types in Swift.

---

### Generic Functions
Generic functions can work with any type. The generic version of the function uses a placeholder type name instead of an actual type name. The actual type to use in place of placeholder is determined each time when generic function is called.

-  **Type parameters** specify and name a placeholder type, and are written immediately after the function’s name, between a pair of matching angle brackets (such as \<T\>). Multiple type parameters separated by comma can be specified.

example :

``` swift
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
```
---

### Generic Types 
These are custom classes, structures, and enumerations that can work with any type.

example :

``` swift
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>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
```
- When you extend a generic type, you don’t provide a type parameter list as part of the extension’s definition. Instead, the type parameter list from the original type definition is available within the body of the extension.

example :

``` swift
extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}
```

- Extensions of a generic type can also include requirements that instances of the extended type must satisfy in order to gain the new functionality using generic **where** clause.

---
### Type Constraints

Type constraints specify that a type parameter must inherit from a specific class, or conform to a particular protocol or protocol composition.

synatx :

`func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}`

Here parameters have type constraints : 

  1. T to be a subclass of SomeClass 
  2. U to conform to the protocol SomeProtocol

example :

``` swift
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
```

---
### Associated Types

An associated type gives a **placeholder name** to a type that’s used as part of the protocol. The actual type to use for that associated type isn’t specified until the protocol is adopted. 

- Associated types are specified with the **associatedtype** keyword.

example :

``` swift
protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
```

``` swift
struct Stack<Element>: Container {
    // original Stack<Element> 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]
    }
}
```

-  Existing type can be **extended** to add conformance to a protocol.

example :

``` swift
extension Array: Container {}
```

- **type constraints** can be added to an associated type in a protocol to require that conforming types satisfy those constraints. 
example :

``` swift
protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
```
- A protocol can appear as part of its own requirements.
example :

``` swift
protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}
```
Suffix has two constraints: It must conform to the SuffixableContainer protocol (the protocol currently being defined), and its Item type must be the same as the container’s Item type. 

example :

``` swift
extension Stack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack {
        var result = Stack()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
```
---
### Generic Where Clauses

Type constraints used to define requirements on the type parameters associated with a generic function, subscript, or type.

To define requirements for associated types, define a generic where clause. A generic **where** clause enables you to require that 
  1. an associated type must conform to a certain protocol, or
  2. certain type parameters and associated types must be the same.

example :

``` swift
func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: 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're equivalent.
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }

        // All items match, so return true.
        return true
}
```

``` swift
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")

var arrayOfStrings = ["uno", "dos", "tres"]

if allItemsMatch(stackOfStrings, arrayOfStrings) {
    print("All items match.")
} else {
    print("Not all items match.")
} // Prints "All items match."
```
Here even though the stack and the array are of a different type, they both conform to the Container protocol, and both contain the same type of values. 

- generic where clause as part of an extension.

example :

``` swift
extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}
```

---
### Contextual Where Clauses

 generic where clause can be included as part of a declaration that doesn’t have its own generic type constraints, when you’re already working in the context of generic types.

``` swift
extension Container {
    func average() -> Double where Item == Int {
        var sum = 0.0
        for index in 0..<count {
            sum += Double(self[index])
        }
        return sum / Double(count)
    }
    func endsWith(_ item: Item) -> Bool where Item: Equatable {
        return count >= 1 && self[count-1] == item
    }
}
```
is same as

``` swift
extension Container where Item == Int {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += Double(self[index])
        }
        return sum / Double(count)
    }
}
extension Container where Item: Equatable {
    func endsWith(_ item: Item) -> Bool {
        return count >= 1 && self[count-1] == item
    }
}
```

---

### Associated Types with a Generic Where Clause
generic where clause can be included on an associated type.

example :

``` swift
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
}
```
---
### Generic Subscripts

Subscripts can be generic, and they can include generic where clauses.
example :

``` swift
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
    }
}
```
The generic where clause requires that the iterator for the sequence must traverse over elements of type Int. This ensures that the indices in the sequence are the same type as the indices used for a container.