iOS & Swift Tutorials

Learn iOS development in Swift. Over 2,000 high quality tutorials!

Overloading Custom Operators in Swift

In this Swift tutorial, you’ll learn how to create custom operators, overload existing operators and set operator precedence.

4.8/5 4 Ratings

Version

  • Swift 5, iOS 13, Xcode 11
Update note: Owen Brown updated this tutorial for Xcode 11, iOS 13 and Swift 5. Evan Dekhayser wrote the original.

Operators are the core building blocks of any programming language. Can you imagine programming without using + or =?

Operators are so fundamental that most languages bake them in as part of their compiler (or interpreter). The Swift compiler, on the other hand, doesn’t hard code most operators, but instead provides libraries a way to create their own. It leaves the work up to the Swift Standard Library to provide all of the common ones you’d expect. This difference is subtle but opens the door for tremendous customization potential.

Swift operators are particularly powerful because you can alter them to suit your needs in two ways: assigning new functionality to existing operators (known as operator overloading), and creating new custom operators.

Throughout this tutorial, you’ll use a simple Vector struct and build your own set of operators to help compose different vectors together.

Getting Started

Open Xcode and create a new playground by going to File ▶ New ▶ Playground. Pick the Blank template and name your playground CustomOperators. Delete all the default code so you can start with a blank slate.

Add the following code to your playground:

struct Vector {
  let x: Int
  let y: Int
  let z: Int
}

extension Vector: ExpressibleByArrayLiteral {
  init(arrayLiteral: Int...) {
    assert(arrayLiteral.count == 3, "Must initialize vector with 3 values.")
    self.x = arrayLiteral[0]
    self.y = arrayLiteral[1]
    self.z = arrayLiteral[2]
  }
}

extension Vector: CustomStringConvertible {
  var description: String {
    return "(\(x), \(y), \(z))"
  }
}

Here you define a new Vector type with three properties conforming to two protocols. The CustomStringConvertible protocol and the description computed property let you print a friendly String representation of the Vector.

At the bottom of your playground, add the following lines:

let vectorA: Vector = [1, 3, 2]
let vectorB = [-2, 5, 1] as Vector

You just created two Vectors with simple Arrays, and with no initializers! How did that happen?

The ExpressibleByArrayLiteral protocol provides a frictionless interface to initialize a Vector. The protocol requires a non-failable initializer with a variadic parameter: init(arrayLiteral: Int…).

The variadic parameter arrayLiteral lets you pass in an unlimited number of values separated by commas. For example, you can create a Vector such as Vector(arrayLiteral: 0) or Vector(arrayLiteral: 5, 4, 3).

The protocol takes convenience a step further and allows you to initialize with an array directly, as long as you define the type explicitly, which is what you’ve done for vectorA and vectorB.

The only caveat to this approach is that you have to accept arrays of any length. If you put this code into an app, keep in mind that it will crash if you pass in an array with a length other than exactly three. The assert at the top of the initializer will alert you in the console during development and internal testing if you ever try to initialize a Vector with less than or more than three values.

Vectors alone are nice, but it would be even better if you could do things with them. Just as you did in grade school, you’ll start your learning journey with addition.

Overloading the Addition Operator

A simple example of operator overloading is the addition operator. If you use it with two numbers, the following happens:

1 + 1 // 2

But if you use the same addition operator with strings, it has an entirely different behavior:

"1" + "1" // "11"

When + is used with two integers, it adds them arithmetically. But when it’s used with two strings, it concatenates them.

In order to overload an operator, you have to implement a function whose name is the operator symbol.

Note: You may define the overload function as a member of a type, which is what you’ll do in this tutorial. When doing so, it must be declared static so it’s accessible without an instance of the type that defines it.

Add the following piece of code at the end of your playground:

// MARK: - Operators
extension Vector {
  static func + (left: Vector, right: Vector) -> Vector {
    return [
      left.x + right.x,
      left.y + right.y,
      left.z + right.z
    ]
  }
}

This function takes two vectors as arguments and return their sum as a new vector. To add vectors, you simply need to add their individual components.

To test this function, add the following to the bottom of your playground:

vectorA + vectorB // (-1, 8, 3)

You can see the resultant vector in the right-hand sidebar in the playground.

Other Types of Operators

The addition operator is what is known as an infix operator, meaning that it is used between two different values. There are other types of operators as well:

  • infix: Used between two values, like the addition operator (e.g., 1 + 1)
  • prefix: Added before a value, like the negation operator (e.g., -3).
  • postfix: Added after a value, like the force-unwrap operator (e.g., mayBeNil!)
  • ternary: Two symbols inserted between three values. In Swift, user defined ternary operators are not supported and there is only one built-in ternary operator which you can read about in Apple’s documentation.

The next operator you’ll want to overload is the negation sign, which will change the sign of each component of the Vector. For example, if you apply it to vectorA, which is (1, 3, 2), it returns (-1, -3, -2).

Add this code below the previous static function, inside the extension:

static prefix func - (vector: Vector) -> Vector {
  return [-vector.x, -vector.y, -vector.z]
}

Operators are assumed to be infix, so if you want your operator to be a different type, you’ll need to specify the operator type in the function declaration. The negation operator is not infix, so you add the prefix modifier to the function declaration.

At the bottom of your playground, add the line:

-vectorA // (-1, -3, -2)

Check for the correct result in the sidebar.

Next is subtraction, which I will leave to you to implement yourself. When you finish, check to make sure your code is similar to mine. Hint: subtraction is the same thing as adding a negative.

Give it a shot, and if you need help, check the solution below!

[spoiler title=”Solution”]

static func - (left: Vector, right: Vector) -> Vector {
  return left + -right
}

[/spoiler]

Test your new operator out by adding this code to the bottom of your playground:

vectorA - vectorB // (3, -2, 1)

Mixed Parameters? No Problem!

You can also multiply vectors by a number through scalar multiplication. To multiply a vector by two, you multiply each component by two. You’re going to implement this next.

One thing you need to consider is the order of the arguments. When you implemented addition, order didn’t matter because both parameters were vectors.

For scalar multiplication, you need to account for Int * Vector and Vector * Int. If you only implement one of these cases, the Swift compiler will not automatically know that you want it to work in the other order.

To implement scalar multiplication, add the following two functions below the subtraction function you’ve just added:

static func * (left: Int, right: Vector) -> Vector {
  return [
    right.x * left,
    right.y * left,
    right.z * left
  ]
}

static func * (left: Vector, right: Int) -> Vector {
  return right * left
}

To avoid writing the same code multiple times, your second function simply relays its arguments to the first one.

In mathematics, vectors have another interesting operation known as the cross-product. How cross-products work is beyond the scope of this tutorial, but you can learn more about them on the Cross product Wikipedia page.

Since using custom symbols is discouraged in most cases (who wants to open the Emoji menu while coding?), it would be very convenient to reuse the asterisk for cross-product operations.

Cross-products, unlike scalar multiplication, take two vectors as arguments and return a new vector.

Add the following code to add the cross-product implementation after the multiplication function you’ve just added:

static func * (left: Vector, right: Vector) -> Vector {
  return [
    left.y * right.z - left.z * right.y,
    left.z * right.x - left.x * right.z,
    left.x * right.y - left.y * right.x
  ]
}

Now, add the following calculation to the bottom of your playground, leveraging both your multiplication and cross-product operators:

vectorA * 2 * vectorB // (-14, -10, 22)

This code finds the scalar multiple of vectorA and 2, then finds the cross-product of that vector with vectorB. Note that the asterisk operator always goes from left to right, so the previous code is the same as if you had used parentheses to group the operations, like (vectorA * 2) * vectorB.

Protocol Operators

Some operators are required members of protocols. For example, a type that conforms to Equatable must implement the == operator. Similarly, a type that conforms to Comparable must implement at least < and ==, because Comparable inherits from Equatable. Comparable types may also optionally implement >, >=, and <=, but these operators have default implementations.

For Vector, Comparable doesn't really make a lot of sense, but Equatable does, since two vectors are equal if their components are all equal. You’ll implement Equatable next.

To conform to the protocol, add the following code at the end of your playground:

extension Vector: Equatable {
  static func == (left: Vector, right: Vector) -> Bool {
    return left.x == right.x && left.y == right.y && left.z == right.z
  }
}

Add the following line to the bottom of your playground to test this out:

vectorA == vectorB // false

This line returns false as expected, because vectorA has different components than vectorB.

Conforming to Equatable gives you more than the ability to check for equality of these types. You also gain access to contains(_:) for an Array of Vectors for free!

Creating Custom Operators

Remember how I said that using custom symbols is usually discouraged? As always, there are exceptions to the rule.

A good rule of thumb about custom symbols is that you should only use them if the following are true:

  • Their meanings are well-known or would make sense to someone reading the code.
  • They are easy to type on the keyboard.

This last operator you will implement matches both of these conditions. The vector dot-product takes two vectors and returns a single scalar number. Your operator will multiply each value in a vector by its counterpart in the other vector, then add up all these products.

The symbol for dot-product is , which you can easily type using Option-8 on your keyboard.

You might be thinking, "I can just do the same thing I did with every other operator in this tutorial, right?"

Unfortunately, you can't do that just yet. In the other cases, you are overloading an operator that already exists. For new custom operators, you need to create the operator first.

Directly underneath the Vector implementation, but above the CustomStringConvertible conformance extension, add the following declaration:

infix operator •: AdditionPrecedence

This defines as an operator that must be placed between two other values and has the same precedence as the addition operator +. Ignore precedence just for the moment because you'll come back to it.

Now that this operator has been registered, add its implementation at the end of your operators extension, immediately below the implementation of the multiplication and cross-product operators *:

static func • (left: Vector, right: Vector) -> Int {
  return left.x * right.x + left.y * right.y + left.z * right.z
}

Add the following code to the bottom of your playground to test this out:

vectorA • vectorB // 15

Everything looks good so far...or does it? Try the following code at the bottom of the playground:

vectorA • vectorB + vectorA // Error!

Xcode isn't very happy with you. But why?

Right now, and + have the same precedence, so the compiler parses the expression from left to right. The compiler interprets your code as:

(vectorA • vectorB) + vectorA

This expression boils down to Int + Vector, which you haven’t implemented and don't plan to implement. What can you do to fix this?

Precedence Groups

All operators in Swift belong to a precedence group, which describes the order in which operators should be evaluated. Remember learning the order of operations in elementary school math? That is essentially what you're dealing with here.

In the Swift standard library, the order of precedence is as follows:

Swift custom operators precedence and associativity table

Here are a few notes about these operators, since you may not have seen them before:

  1. Bitwise shift operators, << and >>, are used for binary calculations.
  2. You use casting operators, is and as, to determine or change a value's type.
  3. The nil coalescing operator, ??, helps providing a fallback value for optional values.
  4. If your custom operator does not specify a precedence, DefaultPrecedence is automatically assigned.
  5. The ternary operator, ? :, is analogous to an if-else statement.
  6. AssignmentPrecedence, for the derivatives of =, is evaluated after everything else, no matter what.

The compiler parses types that have a left associativity so that v1 + v2 + v3 == (v1 + v2) + v3. The opposite is true for right associativity.

Operators are parsed in the order they appear in the table. Try to rewrite the following code using parentheses:

v1 + v2 * v3 / v4 * v5 == v6 - v7 / v8

When you're ready to check your math, look at the solution below.

[spoiler title="Solution"]

(v1 + (((v2 * v3) / v4) * v5)) == (v6 - (v7 / v8))

[/spoiler]

In most cases, you'll want to add parentheses to make your code easier to read. Either way, it's useful to understand the order in which the compiler evaluates operators.

Dot Product Precedence

Your new dot-product doesn’t really fit into any of these categories. It has to be less than addition (as you realized before), but does it really fit into CastingPrecedence or RangeFormationPrecedence?

Instead, you are going to make your own precedence group for your dot-product operator.

Replace your original declaration of the operator with the following:

precedencegroup DotProductPrecedence {
  lowerThan: AdditionPrecedence
  associativity: left
}

infix operator •: DotProductPrecedence

Here, you create a new precedence group and name it DotProductPrecedence. You place it lower than AdditionPrecedence because you want addition to take precedence. You also make it left-associative because you want it evaluated from left-to-right as you do in addition and multiplication. Then you assign this new precedence group to your operator.

Note: In addition to lowerThan, you can also specify higherThan in your DotProductPrecedence. This becomes important if you have multiple custom precedence groups in a single project.

Your old line of code now runs and returns as expected:

vectorA • vectorB + vectorA // 29

Congratulations — you've mastered custom operators!

Where to Go From Here?

You can download the completed version of the playground using the Download Materials button at the top or bottom of this tutorial.

At this point, you know how to bend Swift operators to your needs. In this tutorial, you focused on using operators in a mathematical context. In practice, you’ll find many more ways to use operators.

A great demonstration of custom operator usage can be seen in the ReactiveSwift framework. One example is <~ for binding, an important function in reactive programming. Here is an example of this operator in use:

let (signal, _) = Signal<Int, Never>.pipe()
let property = MutableProperty(0)
property.producer.startWithValues {
  print("Property received \($0)")
}

property <~ signal

Cartography is another framework that heavily uses operator overloading. This AutoLayout tool overloads the equality and comparison operators to make NSLayoutConstraint creation simpler:

constrain(view1, view2) { view1, view2 in
  view1.width   == (view1.superview!.width - 50) * 0.5
  view2.width   == view1.width - 50
  view1.height  == 40
  view2.height  == view1.height
  view1.centerX == view1.superview!.centerX
  view2.centerX == view1.centerX

  view1.top >= view1.superview!.top + 20
  view2.top == view1.bottom + 20
}

Additionally, you can always reference the official custom operator documentation from Apple.

Armed with these new sources of inspiration, you can go out into the world and make your code simpler with operator overloading. Just be careful not to go too crazy with custom operators! :]

If you have any questions or comments on this tutorial, please join the discussion below!

Average Rating

4.8/5

Add a rating for this content

4 ratings

Contributors

Comments