Write code in a more abstract way. It make the code flexible and more reusable.

Array and Dictionary types are both generic collections.

The Problem That Generics Solve

If the code with no generics:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let tmp = a
    a = b
    b = tmp
}

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let tmp = a
    a = b
    b = tmp
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let tmp = a
    a = b
    b = tmp
}

// Use the function
var aInt = 3, bInt = 4
var aString = "Mike", bString = "Amy"
var aDouble = 1.1, bDouble = 2.5

swapTwoInts(&aInt, &bInt)
swapTwoStrings(&aString, &bString)
swapTwoDoubles(&aDouble, &bDouble)

print(aInt, bInt)
print(aString, bString)
print(aDouble, bDouble)

// Print:
// 4 3
// Amy Mike
// 2.5 1.1

Once the code use generics:

// The <T> means that this function will use a generic and the type is T.
func swapTwoObj<T>(_ a: inout T, _ b: inout T) {
    let tmp = a
    a = b
    b = tmp
}

// Use the function
var aInt = 3, bInt = 4
var aString = "Mike", bString = "Amy"
var aDouble = 1.1, bDouble = 2.5

swapTwoObj(&aInt, &bInt)
swapTwoObj(&aString, &bString)
swapTwoObj(&aDouble, &bDouble)

print(aInt, bInt)
print(aString, bString)
print(aDouble, bDouble)

// Print:
// 4 3
// Amy Mike
// 2.5 1.1

So the generics can clearly reduce the duplicated code when the code do the similar things.

Type Parameters

We can define more than one generic types in the angle brackets.

func swapTwoObj<T, C, D>(_ a: inout T, _ b: inout T, _ c: C, _ d: D) {
    let tmp = a
    a = b
    b = tmp
}

Generic Types

The generics can be applied to classes, structures and enumerations.

An example that write a generic stack : First In First Out

class Stack<T> {
    var store: [T] = []
    func push(_ element: T) {
        store.append(element)
    }
    
    func pop() -> T? {
        if store.count > 0 {
            var t = store[store.count - 1]
            store.remove(at: store.count - 1)
            return t
        }
        return nil
    }
    
    func isEmpty() -> Bool {
        return store.count <= 0
    }
}

let stack = Stack<Int>()
stack.push(4)
stack.push(22)
stack.push(6)

while !stack.isEmpty() {
    if let e = stack.pop() {
        print(e)
    } else {
        print("e is nil")
    }
}

// Print: 
// 6
// 22
// 4

Extending a Genetic Type

extension Stack {
    var topItem: T? {
        return store.isEmpty ? nil : store[store.count - 1]
    }
}
stack.push(5)
stack.push(8)
if let top = stack.topItem {
    print(top) // 8
}

Type Constraints

Here we constrain the genetic types that they should conform to the specific protocol.

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

Example:

The T: Equatable define that the T must conform to the Equatable protocol so that the T can be compared with ==.

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

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]

if let foundIndex = findIndex(of: "dog", in: strings) {
    print("Index: \(foundIndex)")
}

// Print:
// Index: 1

Associated Type

The actual type to use for that associated type isn’t specified until the protocol is adopted.

// The conforming type must provide these three requirements.
protocol Container {
    associatedtype Item   // The actual type will be infered when the append method is implemented.
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

The IntStack implementation

struct IntStack: Container {
    var items: [Int] = []
    // It demonstrates the associatedtype.
    typealias Item = Int
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    mutating func append(_ item: Int) {
        push(item)
    }
    
    var count: Int {
        items.count
    }
    
    subscript(i: Int) -> Int {
        items[i]
    }
    
}

The generic stack conform to the container.

struct Stack<Element>: Container {
    var items: [Element] = []
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element{
        return items.removeLast()
    }
    
    // It implements the appen(_: ) requirement, Swift can therefore infer that Element is the associatedtype for the contaioner.
    mutating func append(_ item: Element) {
        push(item)
    }
    var count: Int {
        items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

Extending an Existing Type to Specify an Associated Type

Swift’s Array type already provides append(_: ), count, subscript. This means that we can extend Array to conform the Container protocol.

extension Array: Container {}

Adding Constraints to an Associated Type

protocol Container {
    associatedtype Item: Equatable // Here the Item conform to the Equatable is a constraints
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

Using a Protocol in its Associated Type’s Constraints

Here using the custom protocol SuffixableContainer in its constraints.

protocol SuffixableContainer: Container {
    // It defines an associatedtype named Suffix and its Item type must be the same as the container's Item type.
    associatedtype Suffix: SuffixableContainer *where* Suffix.Item == Item  // Use the protocol suffixableContainer to constrain the associated type. 
    func suffix(_ size: Int) -> Suffix
}

// The extension 
extension Stack: SuffixableContainer {
    // It implement the method, and Swift infer that Suffix is Stack.
    func  suffix(_ size: Int) -> Stack{
        var result = Stack()
        for index in (count - size)..<count {
            result.append(self[index])
        }
        return result
    }
}

var stackOfInt = Stack<Int>()
stackOfInt.push(3)
stackOfInt.push(6)
stackOfInt.push(9)
let suffix = stackOfInt.suffix(2)
print(suffix)

// Print:
// Stack<Int>(items: [6, 9])

Generic Where Clauses

A generic where clause require that an associated type must conform to a certain protocol, or that the certain type parameters and associated types must be the same.

func allItemMatch<C1: Container, C2: Container>(_ container: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
    // Check if two container's count equal.
    if container.count != anotherContainer.count {
        return false
    }
    // Check each item pairs to see if they are equivalent.
    for i in 0..<container.count {
        if container[i] != anotherContainer[i] {
            return false
        }
    }
    
    return true
}

We already extends the array with the code **extension** Array: Container {} before, now both the Stack<String> and Array  are conform to the Container, so they can be the parameter for the method allItemMatch.

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tree")

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

// Now C1 is Stack<String> and C2 is Array
if allItemMatch(stackOfStrings, arrayOfStrings) {
    print("Matched")
} else {
    print("Not match")
}

Extension with a Generic Where Clause

It means that the extension have some condition to be valid. If the stack whose elements aren’t equatable and it try to call the isTop(_ :) , it will trigger a compile-time error.

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tree")

// The extension only be vaild where the element conform to the Equatable protocol.
extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

print(stackOfStrings.isTop("tree"))

We can also use generic where clause with extension to a protocol.

extension Container where Item: Equatable {
    func  startWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

We can also constrain the extension’s generic type must be a specific type. Only when the condition is satisfied, the extension can be valid.

extension Container where Item == Double {
    func average() -> Double {
        var sum = 0.0
        for i in 0..<count {
            sum += self[i]
        }
        return  sum / Double(count)
    }
    
}

print([11.2, 5.3, 3.2].average()) // 6.566666666666666

Contextual Where Clauses

Use the condition to constrain only the method. These method only be available when the condition is satisfied.

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

The same behavior implemented in another way. Moving those requirement in different extensions.

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
    }
}

Two way to do the where clause above have the same behavior. They activate the specific requirements when the condition is satisfied. However, the contextual where clause only need one extension, and the extensions’ generic where clause will require one extension for per requirement.

Associated Type with a Generic Where Clause

Here we define the Iterator conform to the IteratorProtocol where the type Iterator.Element is the same as Item.

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
}

We can add a constraint to an inherited associated type by including the generic where  clause in the protocol declaration.

protocol  ComparableContainer: Container where Item: Comparable {}

Generic Subscripts

Use the where to constrain.

extension Container {
    subscript<Indices: Sequence>(indices: Indices) -> [Item] where Indices.Iterator.Element == Int{
        var result: [Item] = []
        for i in indices {
            result.append(self[i])
        }
        return  result
    }
}