Swift Generics

Filed Under: Swift

In this tutorial, we’ll be discussing Generics in Swift. We’ll be seeing how we can extend Generic types, use Type Constraints on Generics.

Swift Generics

Generics are used to write flexible and reusable code which can work across multiple types. So instead of creating a different function/struct for every type such as Int, String etc, we can set a Generic type. Generic type is also known as a placeholder type.

Typically when we pass typed parameters in a function we do something like:


func printMe(a: Int)
{
    print(a)
}
printMe(a: 1)

func printMe(a: String)
{
    print(a)
}
printMe(a: "Anupam")

Now we can easily use generic parameters instead of creating a different function for each type.

For creating a generic function you need to set a placeholder value after the function name in angular brackets as: <Element>.

You need to use the same placeholder value as parameters/return types.

You can pass more than one placeholder values too.

Typically, if the generic parameter placeholder doesn’t represent anything, use T, U, V etc.


func printMe<T>(a: T)
{
    print(a)
}
printMe(a: 1)
printMe(a: "Anupam")

Another example: The classical way of swapping values:


func swapAandB<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
var x = "hello"
var y = "world"

swapAandB(&x, &y)
print(x)
print(y)

var x1 = 2
var y1 = 3
swapAandB(&x1, &y1)
print(x1)
print(y1)

swift generic swap function

You can only pass the same type in all the T’s. Setting different types would cause compiler error.

We can tackle the above rule by using two generic parameters. We’ll have to declare both of them in the function header itself:


func printTandU<T,U>(a:T,b:U)
{
    print("T is \(a) and U is \(b)")
}

printTandU(a: 1, b: "Swift Generics")

Generic Type Constraints

We can constrain a Generic type to conform to a certain type also.


class Person{

    var name:String?
    var age:Int
    
    init(name:String, age:Int)
    {
        self.name = name
        self.age = age
    }
}

func printDetails<T: Person>(a: T, b: T)
{
    print("a is \(a.name ?? "NA") and age \(a.age)")
    print("b is \(b.name ?? "NA") and age \(b.age)")
}

var p1 = Person(name: "Harry Potter",age: 11)
var p2 = Person(name: "Dumbledore",age: 700)

printDetails(a: p1, b: p2)

T conforms to the type Person. So you cannot pass any value that isn’t of the type Person in the above code.

Doing the following would lead to a crash.


class A{
    
}
printDetails(a: A(), b: A()) //Class A isn't of the type Person.
Subclasses would conform to the the generic parameter type.

Another example where the generic type must conform to the protocol:


func compareAandB<T: Equatable>(a: T, b: T)
{
    print("a == b ? \(a==b)")
}

compareAandB(a: 2, b: 3)
compareAandB(a: "Hi", b: "Hi")

swift generic equatable

Here the == won’t work without the Equatable protocol.

Using Extensions on Generic Types

We can use Extensions on a Generic type in the following way:


struct Stack<T> {
    var items = [T]()
    mutating func push(_ item: T) {
        items.append(item)
    }
    mutating func pop() -> T {
        return items.removeLast()
    }
}
var stackOfInt = Stack<Int>()
stackOfInt.push(2)
stackOfInt.push(3)

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

We DO NOT have to set the Generic type in the Extension again.

Using the where clause

We can use a where clause for an even stricter Generic type constraint checking. In the where clause we can add additional conditions.


protocol Hogwarts{}
protocol Muggles{}

class Person : Hogwarts{

    var name:String?
    var age:Int

    init(name:String, age:Int)
    {
        self.name = name
        self.age = age
    }
}

class M: Person, Muggles{}
func printDetails<T:Person>(a: T) where T:Muggles {
    print("a is \(a.name ?? "NA") and age \(a.age)")
}

var p1 = Person(name: "Harry Potter",age: 11)
var m2 = M(name: "Hermione",age: 700)
var p3 = Person(name: "Ron",age: 11)

printDetails(a: m2)
//printDetails(a: p1)

So in the above code, we add a checker wherein the type T must conform to Muggles protocol as well besides conforming to the class Person.

Hence it’ll take only types of the class M in the above code. Not of the class Person.

swift generics where clause

This brings an end to this tutorial on Swift Generics.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages