Note from Ray: This is a brand new Swift tutorial released as part of the iOS 8 Feast. Enjoy!
As you’ve learned in earlier tutorials in the iOS 8 Feast, Swift offers many powerful modern programming features, such as generics, functional programming, first class enums and structs, and more.
But there’s another new feature of Swift that you should know and love: operator overloading!
This is a fancy way of saying you can make operators like +
, -
, /
, or *
to work with any type you’d like! You can even define your own operators if you’re feeling especially creative.
For example, we use operator overloading in our Swift Sprite Kit utility library to add and multiply CGPoints
like this:
let pt1 = CGPoint(x: 10, y: 20) let pt2 = CGPoint(x: -5, y: 0) let pt3 = pt1 + pt2 let pt4 = pt3 * 100 |
Handy, eh? Get ready to overload your Swift development powers – to over 9,000!
Operators: An Overview
Begin by creating a new playground to help you explore operators.
Add the following line to your playground:
var simpleSum = 1 + 3 |
You’ll see the expected result:
4
There are two familiar operators in play here:
- First, you define a variable named
simpleSum
and set its value with the assignment operator (=
). - Second, you sum the two integers using the addition operator (
+
).
You’ll be overriding operators like these in this tutorial. But first, you need to understand the concept of precedence.
Precedence
You may remember from math class in school that rules of precedence apply to operators. These rules give some operators higher priority than others; higher-priority operators are applied first. For instance, you multiply before adding or subtracting.
Enter the following in your playground to confirm Swift operators follow these same rules:
var sumWithMultiplication = 1 + 3 - 3 * 2 |
You’ll see the following result:
-2
In cases where arithmetic operators have the same precedence, Swift evaluates the operators from left to right. In this example, this means operations are completed in the following order:
3 * 2
: subtraction.1 + 3
: Because the leftmost operator is applied first for operations with equal precedence.4 – 6
: This operation is completed upon the results of the prior operations.
Adding Isn’t Just for Ints
Integer arithmetic works as expected. But can you use the +
operator for other types as well?
It turns out you can! Try it for yourself by adding this line to your playground:
var sumArray = [1, 2] + [1, 2] |
You might expect this to add each element of the same position together. Instead, you’ll see something like this:
[1, 2, 1, 2]
In this case, Swift interprets the +
as an append instruction. But what if you did want to add each element by position? This is known as vector addition.
Well, you could add a custom function to do this. Give it a try by adding the following to your playground:
func add(left: [Int], right: [Int]) -> [Int] { var sum = [Int]() assert(left.count == right.count, "vector of same length only") for (key, v) in enumerate(left) { sum.append(left[key] + right[key]) } return sum } |
Here you define a global function that takes two integer arrays as input, checks that they have the same length, sums the values of each vector element and stores the resulting values in a new array.
Now add the following to confirm that your new function works:
var arr1 = [1, 1] var arr2 = [1, 1] var arr3 = add(arr1, arr2) |
You’ll see the following in the console:
[2, 2]
It works! But rather than having to call a function do this, wouldn’t it be nice to use the +
operator instead?
With Swift, this is possible—with the power of operator overloading!
Operator Overloading
Operator overloading allows you to change the way existing operators work with specific structures or classes. This is exactly what you need – you’d like to change the way the +
operator works with Int
arrays!
Because operator overloading is global to the playground scope, start with a new playground sheet to avoid impacting your earlier examples. Then add the following to your playground:
func +(left: [Int], right: [Int]) -> [Int] { // 1 var sum = [Int]() // 2 assert(left.count == right.count, "vector of same length only") // 3 for (key, v) in enumerate(left) { sum.append(left[key] + right[key]) // 4 } return sum } |
You’ve just defined a global function called +
that takes two Int
arrays as input and returns a single Int
array. Here’s a breakdown of how it works:
- Note there’s nothing fancy about this function definition. It’s a normal function definition except you use
+
for the name! - Here you create an empty
Int
array. - This sample will only work with input arrays of the same length, and this
assert
enforces that. - You then enumerate through the left array, and sum each value with the corresponding value in the right array at the same position.
Test this function by adding the following to your playground:
var sumArray1 = [1, 2, 3] + [1, 2, 3] |
Finally—the expected results of your vector addition operator! You will see the following in the console:
[2, 4, 6]
Of course, operator overloading isn’t all fun and games. It would be fairly confusing to someone jumping into your code if they were expecting the default behavior. For that matter, there’s nothing to stop you from overriding the +
operator to, for example, perform subtraction on integers—the risks are obvious!
Remember the operator overloading mantra: with great power comes great responsibility.
Typically, you’ll use overloading to extend an operation to a new object while maintaining the original semantics, rather than defining different (and confusing) behavior.
In this example, the behavior override does maintain semantics; vector addition is still a form of addition. But you may still want the ability to append arrays, and you want to avoid confusing yourself months down the road when you’ve forgotten you overrode the default behavior for Int
arrays.
Fortunately, Swift lets you create your own custom operators.
Defining Custom Operators
There are three steps to define a custom operator:
- Name your operator
- Choose a type
- Assign precedence and associativity
Let’s go over these one by one.
Naming Your Operator
Now, you have to pick a character for your operator. Custom operators can begin with one of the ASCII characters /
, =
, -
, +
, !
, *
, %
, <
, >
, &
, |
, ^
, or ~
, or with one of the Unicode characters. This gives you a broad range of possible characters. Don’t go crazy, though—remember that you’ll have to type these characters repeatedly and the fewer keystrokes, the better.
In this case, you’ll copy and paste the Unicode symbol ⊕
, a representation of direct sum that fits your purposes nicely.
Choosing a Type
With Swift, you can define binary, unary and ternary operators. These indicate the number of targets involved.
- Unary operators involve a single target and are defined either as postfix (
i++
) or prefix (++i
), depending on where they appear in relation to the target. - Binary operators are infix because they appear in between the two targets, for example
1 + 1
. - Ternary operators operate on three targets. In Swift, the conditional operator is the only Ternary operator, for example
a ? b : c
.
You should choose between these based on the number of targets of your operation. You want to add two arrays (i.e. 2 targets), so you will define a binary operator.
Assigning Precedence and Associativity
Because operators are defined globally, you need to choose the associativity and precedence of your custom operator with care.
This can be tricky, so a good rule of thumb is to find a comparable standard operator defined in the Swift language reference and use similar semantics. For example, to define vector addition, it makes sense to use the same associativity and precedence as the +
operator.
Coding Your Custom Operator
Back in your playground, enter the following to define your custom operator. You’ll probably want to copy and paste the ⊕ for simplicity.
infix operator ⊕ { associativity left precedence 140 } // 1 func ⊕(left: [Int], right: [Int]) -> [Int] { // 2 var sum = [Int](count: left.count, repeatedValue: 0) assert(left.count == right.count, "vector of same length only") for (key, v) in enumerate(left) { sum[key] = left[key] + right[key] } return sum } |
This code is similar to your earlier override, aside from section 1, where you do all of the following:
- Define an infix/binary operator that operates on two targets and is placed in between those targets.
- Name the operator ⊕.
- Set associativity to left, indicating that operands of equal precedence will use left associativity to determine order of operation.
- Set the precedence to 140, which is the same weight as
Int
addition, as found in the Swift language reference.
The code in section 2 is similar to what you’ve seen so far – it adds the two arrays item by item, based on their order in the arrays. Test the new operator by adding the following to your playground:
var sumArray = [1, 2, 3] ⊕ [1, 2, 3] |
You’ll see the same results as the earlier function and override, but this time, you have different operators for different semantics.
Bonus Round!
Now that you have a good idea of how to create a custom operator, it’s time to challenge yourself. You’ve already created a ⊕ operator to perform vector addition, so use that knowledge to create a ⊖ operator for subtracting Int
arrays in a similar manner. Give it your best shot, and check your solution against the answer below.
Remember Related Operators!
If you define a new operator, don’t forget to define any related operators.
For example, the addition assignment operator (+=
) combines addition and assignment into a single operation. Because your new operator is semantically the same as addition, it’s a good idea to define the assignment operator for it, as well.
Add the following to Operator2.playground:
infix operator ⊕= { associativity left precedence 140 } // 1 func ⊕=(inout left: [Int], right: [Int]) { // 2 left = left ⊕ right } |
Line 1 is exactly the same declaration as for the ⊕ operator, except it uses the compound operator symbol.
The trick is on line 2, where you mark the compound assignment operator’s left input parameter as inout
, which means the parameter’s value will be modified directly from within the operator function. As a result, the operator doesn’t return a value—it simply modifies the input.
Test that this operator works as expected by adding the following to your playground:
sumArray ⊕= [1, 1, 1] |
You’ll see the following in the console:
[3, 5, 7]
As you can see, defining your own operators is not difficult at all! :]
Defining Operators for More Than One Type
Now imagine that you want to define vector addition for decimal points, too.
One way is to overload a new operator for the types Double
and Float
, just like you did for Int
.
It’s just a couple of lines of code, but you’ll have to use copy/paste. If you’re like me—a clean code addict—then duplicating code is not your first choice, as it makes your code harder to maintain.
Generics to the Rescue!
Luckily, Swift generics can help with exactly this! If you need a refresher on Swift generics, check out our Swift Generics Tutorial released earlier this week.
Start a new playground so you have a clean slate. Add the following to your new playground:
infix operator ⊕ { associativity left precedence 140 } func ⊕<T>(left: [T], right: [T]) -> [T] { // 1 var minus = [T]() assert(left.count == right.count, "vector of same length only") for (key, v) in enumerate(left) { minus.append(left[key] + right[key]) // 2 } return minus } |
On line 1, you define a generic function called ⊕ that works with a placeholder type signified by T
. The playground isn’t happy. You’ll see a compile error that looks like this: Could not find an overload for '+' that accepts the supplied arguments.
The error comes from line 2, where you’re attempting to use the +
operator on the values left
and right
, which are the placeholder type. Swift doesn’t know how to apply a +
operand to those variables, because it has no idea what type they are!
Extending With a Protocol
Erase your old code and replace it with the following:
protocol Number { // 1 func +(l: Self, r: Self) -> Self // 2 } extension Double : Number {} // 3 extension Float : Number {} extension Int : Number {} infix operator ⊕ { associativity left precedence 140 } func ⊕<T: Number>(left: [T], right: [T]) -> [T] { // 4 var minus = [T]() assert(left.count == right.count, "vector of same length only") for (key, v) in enumerate(left) { minus.append(left[key] + right[key]) } return minus } |
You’re doing quite a lot here, so let’s take a step back and break it down.
- You define a protocol called
Number
. - The
Number
protocol defines an operator called+
. - You create an extension for
Double
,Float
andInt
data types that causes them to adopt theNumber
protocol. - You use a type constraint that requires
T
to conform to theNumber
protocol.
Ultimately, you’re telling the compiler that the generic type T
understands the +
operand. Now that you’ve cleared the compile error, try out the operand with arrays of Double
s and, separately, with Int
s by adding the following code:
var doubleArray = [2.4, 3.6] ⊕ [1.6, 2.4] var intArray = [2, 4] ⊕ [1, 2] |
You’ll see the following in your console:
[4.0, 6.0] [3, 6]
The operand now works with multiple data types and involves no duplication of code. If you wanted to add more numeric types, you could simply add a markup extension conforming to Number
.
How Can I Use Overloading in Real Life?
Do you think I’d let you spend your time on this tutorial if I wasn’t convinced of its usefulness? :] This section will employ a practical example to give you a better idea of how to use overloading in your own projects.
Operators and CGPoints
For this demo, you’ll use the SKTUtils library, a collection of handy Sprite Kit helper classes, written for the second edition of our iOS Games by Tutorials book.
You can find the repo of the framework on github. Clone the Swift branch of the repo by typing the following into a Terminal window:
git clone https://github.com/raywenderlich/SKTUtils.git --branch swift |
Alternatively, you can download the ZIP of the Swift branch from github.
Note: Since Xcode 6 beta 5, it’s possible to import your own library within a playground sheet. All you need to do is have the framework and playground bundled together in a workspace. If you’d like to know more about this, read my post Playground has never been so fun.
Open SKUTils/Examples/Playground/SKUTils.xcodeworkspace and build the project (you must build the project at least once for the associated framework to be compiled).
Then navigate to MyPlayground.playground from the project navigator. Delete what is currently in the playground and add the following code:
import SKTUtils let pt1 = CGPoint(x: 10, y: 20) let pt2 = CGPoint(x: -5, y: 0) let pt3 = pt1 + pt2 let pt4 = pt3 * 100 |
You might be surprised to see that you’re successfully using the +
and *
operators on a CGPoint
without a word of complaint from the compiler.
{x 10 y 20} {x -5 y 0} {x 5 y 20} {x 500 y 2,000}
The magic here is coming from SKTUtils, which you’ve imported up top. Let’s take a closer look.
Overloading in SKTUtils
Navigate to SKTUtils/CGPoint+Extension.swift in the project navigator. You’ll see that this class defines an extension on CGPoint
that, among other things, overloads the +
operator as well as the corresponding compound assignment (+=
) operator.
public func + (left: CGPoint, right: CGPoint) -> CGPoint { return CGPoint(x: left.x + right.x, y: left.y + right.y) } public func += (inout left: CGPoint, right: CGPoint) { left = left + right } |
The code is similar to what you did earlier, but notice the access control set to public
. Access control restricts access to parts of your code from code in other source files and modules. As SKTUtils is a framework, its utilities need to be accessible from outside its module and it is thus defined as public.
The prestidigitation explained, there is no magic, after all—just smart coding!
Adding, subtracting, and multiplying CGPoint
s is a very common operation in games, so overloading operators to work on CGPoint
s greatly simplified the code in the book, making it much more concise and easier to read. I’m sure you can find similar examples in your own projects!
Operator overloading is a powerful feature of Swift that can make development much more efficient, if you do it with care.
Where to Go From Here?
You’ve reached the end of this tutorial—I hope you enjoyed it! You can find copies of the final playground files here and here.
If you’d like to learn more about operator overloading and Swift in general, check out our new book Swift by Tutorials.
I hope you find a way to use operator overloading in your own projects! But remember, with great power comes great responsibility – don’t be Troll Dev! ;]
If you have any questions or comments on this tutorial or on operator overloading in general, please join the forum discussion below!
Operator Overloading in Swift Tutorial is a post from: Ray Wenderlich
The post Operator Overloading in Swift Tutorial appeared first on Ray Wenderlich.