Swift Generics

Generics are simple yet one of the most powerful features of Swift programming language. Generic code enables us to write flexible and reusable functions that can work with any data type. Most of the Swift standard library is written using generics code. We have been using generic in our projects for a long time without even realizing it, for example, Swift’s collection types I.e. Array and Dictionary are both generic collections as we can create array or dictionary using any specified type including integer, float, double, etc.

Problem without Generics

We must understand the problem that we generally face if we don’t use generics in our code. In the following code, a function add Numbers() is supposed to add two numbers that are being passed into it. 

func addNumbers(_ a:Int, _ b:Int) -> Int{

    let sum = a + b

    return sum

}

The function accepts two Integer parameters and returns the sum by adding these two numbers. We can call this method to add two integer numbers. This function is useful but what if, we also need to add the floating-point numbers or double parameters. In that case, we need to create two specific functions again which would accept double and float parameters, As given below.

func addNumbers(_ a:Float, _ b:Float) -> Float{

    let sum = a + b

    return sum

}

If you notice, the body of two functions is similar, the only difference is the parameters that the functions accept. 

In Other Words, we may need to create n number of functions to add n data type of variables. However, this approach is not useful enough since it conflicts the concepts of code reusability and readability in swift.

However, if we could have written a single function that can accept any type of parameters, that would be more flexible and useful.

Generic Functions

Generic functions are the ones that can work with any type. The generic version of the addNumbers() method is given below.

func addNumbers<T:Numeric>(_ a : T, b : T) -> T{

    let sum = a + b

    return sum

}

The body of the generic and non-generic method addNumbers() is similar. However, the two are declared differently. The generic method uses a placeholder (T) instead of an actual data type. The placeholder says that T must be a numeric type (Int, Double, or Float) in this particular case of adding two numeric values. The actual type to use instead of T is determined each time the function gets called. Here, we can pass any numeric type value to this function.

Consider the following example.

class MyCalculation{

func addNumbers<T:Numeric>(_ a : T, b : T) -> T{

    let sum = a + b

    return sum

}

}

var c = MyCalculation()

print(c.addNumbers(10.4, b: 4)) // it prints 14.4

Generic Types

Swift also facilitates us to declare the generic types in addition to Generic functions. The Generic Types are the user-defined classes, structures, and enumerations that can work with any data type similar to array or dictionary.

Here, we will write a generic type Stack that will be an ordered set of values that allows a new item to be inserted only to the end of the collection. Generally, items in the stack can be only be inserted and deleted at one end called top of the stack. The operations are called push and pop.

Non-Generic Stack

The Non-Generic version of the stack is defined below. The struct IntStack can only work with the Integer values. However, the structure uses the array property called elements to store the values. It provides two operations push () and pop () which can push and pop the elements in the stack. The IntStack doesn’t work with any type of values as it is non-generic and can only be useful to the integers.

struct IntStack{

    var elements = Array<Int>()

    mutating func push(_ elem : Int){

        elements.append(elem)

    }   

    mutating func pop() -> Int{

        return elements.removeLast()

    }

}

Generic Stack

If we notice, the non-generic and generic version of the stack is somehow similar. However, the structure Stack can work with any type of values with the type parameter called Element mentioned in the declaration. It defines a placeholder name for a data type which is to be defined later. 

struct Stack<Element>{

    var elements = Array<Element>()

    mutating func push(_ elem : Element){

        elements.append(elem)

    }    

    mutating func pop() -> Element{

        return elements.removeLast()

    }

}

Since we have defined structure stack to be the generic type, now we can use Stack in our code to be the collection of any valid type in the swift, like array and dictionary. Consider the following example which uses Stack to define the stack of integers and strings.

var stackOfInt = Stack<Int>()

stackOfInt.push(10)

stackOfInt.push(20)

print(stackOfInt)

stackOfInt.pop()

print(stackOfInt)


var stackOfStrings = Stack<String>()

stackOfStrings.push("abc")

stackOfStrings.push("name")

print(stackOfStrings)

stackOfStrings.pop()

print(stackOfStrings)

Extending a Generic Type

We can create an extension for the Generic types, however, we need not mention the type parameter list while extending a Generic type as it is already defined in the original type definition and is available in the body of the extension. Original type parameter names are used to refer to the type parameters from the original definition.

In the following example, we have added a read-only computed property topItem to the Stack type, which returns nil if the stack is empty otherwise it returns the topmost element of the stack.

extension Stack{

    var topElement:Element?{

        if elements.isEmpty{

            return nil

        }else{

            return elements[elements.count - 1]

        }

    }

}

Associated Types

Associated types are defined as the part of the protocol to provide a placeholder name to a type that is used in the protocol. The actual type used for that associated type is defined when any class conforms to the protocol. It is specified with the associatetype keyword. 

An associated type Element is defined as the part of the protocol Collection given below. The class which adopts this protocol is provided functionality to append a new element into the collection of type Element. 

The count is the get only property which specifies the count of the collection. The subscript takes an integer index value so that we can retrieve every value in the collection. 

protocol Collection {

    associatedtype Element

    mutating func append(_ element : Element)

    var count:Int{ get }

    subscript(index:Int) -> Element { get }

}

Here, the protocol collection must ensure the right type is being passed into the append () method, and also the subscript returns the same type which gets appended into the collection. For this purpose, the protocol defines an associated type Element which is passed into the append method also returns as a collection element. The conforming class defines the actual type for the associated type defined in the protocol. 

In the following example, our generic type Stack conforms to the Collection protocol and uses the associated type to put the values into it.

struct Stack<Element> : Collection{

    var elements = Array<Element>()

    mutating func push(_ elem : Element){

        elements.append(elem)

    }    

    mutating func pop() -> Element{

        return elements.removeLast()

    }

    typealias Element = Element    

    mutating func append(_ element: Element) {

        self.push(element)

    }    

    var count : Int{

        return elements.count    }    

    subscript(index: Int) -> Element {

        return elements[index]

    }    

}

Also, we can restrict the type parameters associated with the Generic function, type, or subscript. We can define certain requirements on the type parameters associated with the Generic types. However, we have used the type constraints in the very first example of the generic function in this tutorial, where we have restricted the Type Parameter T to be Numeric. 

However, Generics are simple yet one of the most powerful features of the swift programming language which can enhance the code reusability and readability in our iOS projects. 

Leave a Reply