Polymorphic Swift

DAMIAN ROBINSONDAMIAN ROBINSON
5 min read

Polymorphism: The Shape-Shifter of Code

At its core, polymorphism is a fancy way of saying "many forms." It lets you treat different types of objects in a consistent way, which makes your code more flexible and reusable. Think of it like a superhero that can take on different powers as needed—same character, different abilities!

There are two kinds of polymorphism you’ll come across in Swift:

  1. Static Polymorphism (also known as compile-time polymorphism): Swift decides at compile time what method or function to call based on the type.

  2. Dynamic Polymorphism (or run-time polymorphism): Swift figures out which function to call when the program is actually running, based on the actual type of the object.

We’re gonna focus on static polymorphism, especially how it ties into generics, which allows your code to operate on any type, as long as it meets certain requirements. This gives you the power to write functions, types, and even entire classes that are flexible but safe.


The Power of Generics in Polymorphism

Swift generics let you write flexible, reusable functions and types that can work with any data type. You might already know this from our generic intro earlier, but here’s where things get even cooler with polymorphism.

With generics, you can use polymorphism to write a single function that works for multiple types of objects. As long as the types meet the requirements you set (like conforming to a protocol), your code adapts seamlessly.


Polymorphism in Action with a Generic Function

Say you want a function that prints any kind of collection—an array, a set, or a dictionary. Instead of writing a bunch of different functions for each type, you can use generics to create one flexible function that handles them all.

func printElements<T: Collection>(_ collection: T) {
    for element in collection {
        print(element)
    }
}

Here’s what’s happening:

  • The <T: Collection> bit says, "Hey, T can be any type as long as it conforms to the Collection protocol."

  • Inside the function, we loop through the elements and print them, without caring whether T is an array, set, or any other collection.

You just used polymorphism! T is a placeholder that can take the form of any collection type. We’ve written one function that can morph and handle different types, depending on what you pass in. Check it out in action:

let numbers = [1, 2, 3, 4]
let names = Set(["Alice", "Bob", "Charlie"])

printElements(numbers)  // Handles an array of Ints
printElements(names)    // Handles a set of Strings

Same function, but it adapts to different types of collections. That’s static polymorphism at its best!


Polymorphism with Protocols and Generics: Double Power-Up

Now, let’s take it up a notch. What if you want to enforce more specific behavior on the types you’re dealing with? That’s where protocols come in.

You can create a protocol that defines certain behaviors (methods or properties) and then use generics to ensure any type passed in conforms to that protocol. This combo gives you supercharged polymorphism.

Let’s say we want to create a function that prints only collections whose elements can be compared for equality (conforming to the Equatable protocol):

protocol EquatableCollection: Collection where Element: Equatable {}

func printMatchingElements<T: EquatableCollection>(_ collection: T, value: T.Element) {
    for element in collection {
        if element == value {
            print("Found a match: \(element)")
        }
    }
}

Here’s the breakdown:

  • The EquatableCollection protocol ensures that the collection elements can be compared with ==.

  • The printMatchingElements function accepts any collection type that conforms to this protocol and prints matching elements.

Let’s use it:

let numbers = [1, 2, 3, 4]
printMatchingElements(numbers, value: 2)
// Output: Found a match: 2

Now, we’re only allowing collections with equatable elements, enforcing that elements can be compared. That’s polymorphism in action again—different collections, but the same function behavior, thanks to generics and protocols.


Polymorphism in Custom Types

Okay, let’s kick it up even more. You can build your own custom types that use polymorphism through generics. Let’s say you want to build a Storage type that can hold anything, as long as it conforms to Comparable (so you can sort it, find the minimum, maximum, etc.).

struct Storage<T: Comparable> {
    private var items: [T] = []

    mutating func addItem(_ item: T) {
        items.append(item)
    }

    func getItems() -> [T] {
        return items
    }

    func getMax() -> T? {
        return items.max()
    }
}

Here’s what’s happening:

  • Storage is a generic type that can work with any type T, as long as T conforms to Comparable (so we can use the max() method).

  • We’ve got a function to add items and another to get the maximum item.

Check out how flexible this becomes:

var intStorage = Storage<Int>()
intStorage.addItem(5)
intStorage.addItem(10)
print(intStorage.getMax())  // Prints: Optional(10)

var stringStorage = Storage<String>()
stringStorage.addItem("apple")
stringStorage.addItem("banana")
print(stringStorage.getMax())  // Prints: Optional("banana")

Boom! That’s polymorphism with generics in full effect. We’ve built a flexible type that can store any Comparable data and find the max value, whether it’s numbers or strings. One type, many forms—that’s polymorphism, baby.


Wrap Up

Polymorphism with Swift generics is like giving your code superpowers—it becomes flexible, reusable, and adaptable to different types, all while keeping things type-safe. You can write one function, struct, or class that works with a wide range of types, as long as they meet the requirements you set.

When you combine generics with protocols and constraints, you’ve got a powerful toolset that lets you design flexible, clean, and reusable code. It’s like crafting your own universal tool that can morph into whatever shape you need.

Next time you’re writing Swift code, remember: if you want flexibility and safety, polymorphism + generics is the way to go.

0
Subscribe to my newsletter

Read articles from DAMIAN ROBINSON directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

DAMIAN ROBINSON
DAMIAN ROBINSON