Codementor Events

Swift Tips & Tricks: Protocol Extensions in Swift

Published Aug 17, 2015Last updated Feb 22, 2017

Let's say you want a clamped function in Swift, where you'd make sure a given number is limited within a range. So that:

20.clamped(min: 1, max: 10) // gives 10
3.clamped(min: 1, max: 10) // gives 3

Extending Int

What first comes to mind is extending Int struct and adding the method there.

extension Int {
    func clamped(min min: Int, max: Int) -> Int {
        if self < min {
            return min
        }
        
        if self > max {
            return max
        }
        
        return self
    }
}

Awesome. Now our sample code works.

But what if we try to clamp a double?

(20.3).clamped(min: 1, max: 10) // Error: 'Double' does not have a member named 'clamped'

At this point, you might be thinking of extending Double type to add the clamped function there too. But what about UInt? Or CGFloat or Int32? There are a lot of number types in Swift and you can't extend them all...

A Swift Solution

Well, when you inspect our clamped method, you'll see that we actually only use 2 parameters inside it. Apart from > and < operator functions, there is no other functions or methods that we use.

Hmm... Those two look familiar. Inspect the Swift headers and you'll see this:

public protocol Comparable : Equatable {
    public func <(lhs: Self, rhs: Self) -> Bool
    public func <=(lhs: Self, rhs: Self) -> Bool
    public func >=(lhs: Self, rhs: Self) -> Bool
    public func >(lhs: Self, rhs: Self) -> Bool
}

Now with Swift 2.0, we can actually extend Comparable itself! Just replace extension Int with extension Comparable and other Ints with Self and voila:

extension Comparable {
    func clamped(min min: Self, max: Self) -> Self {
        if self < min {
            return min
        }
        if self > max {
            return max
        }
        return self
    }
}

Clamp All the Things

Now every type that conforms to Comparable can be clamped by things of it's own type.

(20.3).clamped(min: 0.9, max: 10.1) // Gives 10.1

let number: CGFloat = 3.2
number.clamped(min: 0.9, max: 10.1) // Gives 3.2

We can even clamp Strings!

"a".clamped(min: "x", max: "z") // Gives x
"y".clamped(min: "x", max: "z") // Gives y

Being able to extend protocols is a huge update to Swift.

Discover and read more posts from Seyithan Teymur
get started