Operators are one of the basic constructs of any programming language. They are represented as symbols and have various associated properties, and understanding them is crucial to mastering any programming language.
In this article, we’ll look at some of the operators that Swift ships with and also create our own operators.
Before we begin looking at the different operators that Swift provides us with and creating custom operators, let’s understand a few basic terms related to them.
Here’s an example:
let a = 5 let b = 10 let sum = a + b
Here, we can see a few variables and constants being defined, along with a few symbols (e.g., =
and +
). Let’s define two terms here:
=
is used to assign and +
is used to add in the above example and are therefore operatorsa
as an operand, and on line 3, we have a
and b
as two operands on which the +
operator is being appliedThere are three different types of operators, which are defined by the number of operands they work on.
=
and !
)+
, -
, *
, etc.?:
Notation defines the position of the operator when used with the operands. There are again three types:
Infix
: when the operators are used in between operands. Binary and ternary operators are always infixed
Prefix
: when the operators are used before an operand, e.g., ++
, --
, and !
Postfix
: when the operators are used after an operand, e.g., …
When working with operators in Swift, you should also know the priority order in which the operator is executed. Operator precedence only matters for infix
operators, as they work on multiple operands.
For example, if an expression has multiple operators in it, Swift needs to know which operator needs to be executed first.
var result = 4 + 6 * 2 // 6*2 is done first which yields 12, then we do 4 + 12 = 16
In the above example, *
has more precedence compared to +
, which is why 6*2
is executed before 4+6
.
In case you have two operators with the same precedence being used in an expression, they’ll fall back on their associativity. Associativity defines the direction in which the expression will start resolving in case the precedence of the operators are the same.
Associativity is of two types:
left
: this means that the expression to the left will be resolved firstright
: this means the expression to the right will be resolved firstFor example, we have the following statement:
let result = 5 + 10 - 2 //13
Because both +
and -
have the same precedence, Swift falls back to the associativity of the operators. +
and -
have associativity of left
, so we resolve the expression from the left so that +
is evaluated first.
Here’s a table of the precedence order that Swift follows. The table lists the precedence in the order of decreasing priority.
Precedence Name | Operators Included |
---|---|
BitwiseShiftPrecendence |
<< , >> , &<< , &>> |
MultiplicationPrecedence |
* , / , % , & , &* |
AdditionPrecedence |
+ , - , | , ^ , &+ , &- |
RangeFormationPrecedence |
..< , ... |
CastingPrecedence |
is , as , as? , as! |
NilCoalescingPrecedence |
?? |
ComparisonPrecedence |
< , <= , > , >= , == , != , === , !== , ~= , .< , .<= , .> , .>= , .== , .!= |
LogicalConjunctionPrecedence |
&& , .& |
LogicalDisjunctionPrecedence |
|| , .| , .^ |
TernaryPrecedence |
?: |
AssignmentPrecedence |
= , *= , /= , %= , += , -= , <<= , >>= , &= , |= , ^= , &*= , &+= , &-= , &<<= , &>>= , .&= , .|= , .^= |
Now that we know the basics of what Swift operators are and how they are used, let’s look at popular operators that ship with Swift.
The most common operators that Swift ships with fall under the following categories:
Assignment operators are used to assign values to a constant or variable. The symbol is =
.
These operators are used to perform basic arithmetic operations, such as +
, -
, *
, /
, and %
.
Logical operators are used to combine two conditions to give a single boolean value output, e.g., !
, &&
, and ||
.
These operators are used to compare numbers and give out a true
or false
output: >
, <
, ==
, >=
, <=
, and !=
.
This operator is a shorthand operator for writing if-else conditions. Even though it does the same job, it is not a good idea to consider it a replacement for if-else conditions because it hampers readability.
Therefore, when you have different code blocks to run for different conditions, it’s better to use if-else blocks. For shorter, simpler cases, use the ternary operator. The operator, ?:
, is used like this:
var value = 5 > 2 ? "5 is greater than 2" : "5 is less than 2"
This is a shorthand operator to return default values in case a particular variable is nil. The operator symbol is ??
and commonly used like this:
var identity: String? = nil let nonNillableIdentity: String = identity ?? "Jane Doe" // since `identity` is nil, we return a default value of Jane Doe and store it in the nonNilalbleIdentity variable
These operators are used to define ranges, e.g., …
and ..<
. They are mainly used with array/strings to extract sub arrays/strings.
var array = [1,2,3,4,5] let a = array[1...3] // [2,3,4] let b = array[1..<3] // [2,3] let c = array[2...] // [3,4,5] let d = array[...3] // [1,2,3,4]
For a list of all the basic operators and their descriptions, read the Swift documentation.
Now that we’ve seen the operators that Swift ships with, let’s create our own operators. Custom operators are generally defined for three purposes:
Let’s look at all three purposes with examples.
Let’s say you want to define an operator with the △
symbol that calculates the hypotenuse of a right angle triangle given its two sides.
infix operator â–ł func â–ł(lhs: Double, rhs: Double) -> Double { return sqrt(lhs*lhs + rhs*rhs) } var hypotenuse = 3â–ł4 // 5
We know that the +
operator is used to add two numbers, but let’s say we wanted to add two instances of our struct called Velocity
, which is a two-dimensional entity:
struct Velocity { var xVelocity: Double var yVelocity: Double }
If we were to run the following command, you’d get a compiler error:
let velA = Velocity(xVelocity: 2, yVelocity: 4) let velB = Velocity(xVelocity: 2, yVelocity: 4) let velC = velA + velB // Binary operator + cannot be applied to two Velocity operands
In this case, it makes sense to define a +
operator that takes care of adding two Velocity
instances:
extension Velocity { static func +(lhs: Velocity, rhs: Velocity) -> Velocity { return Velocity(xVelocity: lhs.xVelocity + rhs.xVelocity, yVelocity: lhs.yVelocity + rhs.yVelocity) } }
Now the earlier statement will execute properly.
Let’s say you have a struct that is used to define an item in a supermarket.
struct Item: Equatable { let id = UUID() let itemName: String let itemType: String let itemPrice: Double }
When we need to compare two items, such that if two items have the same itemName
, itemType
, and itemPrice
, we need to consider them equal regardless of what their id
is.
If you run the following piece of code, you’ll see that the two variables are not equal:
let detergentA = Item(itemName: "Tide", itemType: "Detergent", itemPrice: 56.5) let detergentB = Item(itemName: "Tide", itemType: "Detergent", itemPrice: 56.5) let areDetergentsEqual = detergentA == detergentB // false
The reason for this is that instances detergentA
and detergentB
have different ids
. In this case, we need to override the ==
operator and custom logic to determine equality.
extension Item { static func ==(lhs: Item, rhs: Item) -> Bool { return lhs.itemName == rhs.itemName && lhs.itemPrice == rhs.itemPrice && lhs.itemType == rhs.itemType } }
Now that we know the different scenarios in which we define custom operators, let’s define our custom operator.
A custom operator is defined just like a function is defined in Swift. There are two types of operators you can define:
The definition looks like the following:
func <operator symbol>(parameters) { // operator logic }
There are different types of parameters you can define based on whether it is an infix
or a prefix
/postfix
operator.
If it’s an infix
operator, you can have two parameters, and, when it’s a prefix
/postfix
operator, you can have a single parameter.
With global operators, we first define the operator with the notation keyword (infix
, prefix
or postfix
) and the operator
keyword, like this:
<notation> operator <operator symbol>
Here’s an example:
infix operator ^ // the operator is an infix operator with the symbol ^
When we want to define an operator for a class or a struct, we need to define the operator function as a static function. The notation parameter is optional in case the operator is an infix
operator (i.e., you have two parameters in the function signature), else you need to specify prefix
or postfix
:
extension <class name> { static <notation> func ^(parameters) { // note that this needs to be static function // operator logic } }
Given that we’ve already looked at defining basic custom operators like +
and ==
, let’s review some examples of how you can define custom compound operators or operators that mutate the parameters themselves.
We’ll do this by defining a custom compound arithmetic operator for the Velocity
struct from earlier:
extension Velocity { static func +=(lhs: inout Velocity, rhs: Velocity) { // note that the first parameter is an inout variable lhs.xVelocity = lhs.xVelocity + rhs.xVelocity lhs.yVelocity = lhs.yVelocity + rhs.yVelocity } }
Now let’s execute the following statement:
var velA = Velocity(xVelocity: 5, yVelocity: 5) var velB = Velocity(xVelocity: 5, yVelocity: 5) velA += velB print(velA) // Velocity(xvelocity: 10.0, yVelocity: 10.0)
Now, we’ll specify the notation of a custom operator.
In order to define the notation of a custom operator, you have three keywords for the corresponding notation:
infix
postfix
prefix
When defining a global operator, you need to first define the operator’s notation, then define its functions. Note that this is not a compulsory step, but it’s necessary if you want to define global operators or hope to assign a precedence to it (which we’ll discuss in the next section).
Let’s look at the hypotenuse operator again, which is being defined as a global operator. You can see that we first define the operator with the notation and the symbol, then implement its logic:
infix operator â–ł // define the operator with its notation and symbol func â–ł(lhs: Double, rhs: Double) -> Double { // implement the operator logic return sqrt(lhs*lhs + rhs*rhs) } var hypotenuse = 3â–ł4 // 5
When defining an operator for your own class/struct, you can directly use the notation keyword with the method definition. For example, let’s define a negation operator for Velocity
as well, which negates both the xVelocity
and yVelocity
properties. This operator is going to be defined as a prefix
operator:
extension Velocity { // defining a prefix negation operator static prefix func -(operand: Velocity) -> Velocity { return Velocity(xVelocity: -val.xVelocity, yVelocity: -val.yVelocity) } } let vel = Velocity(xVelocity: 4, yVelocity: 4) print(-vel) // xVelocity: -4, yVelocity: -4
You can also define the precedence of your operator using the precedence keywords, as mentioned in the precedence table above. This is usually done in the definition step:
infix operator ~: AdditionPrecedence // now this operator's priority is the same at the operators mentioned in the AdditionPrecedence func ~(lhs: Int, rhs: Int) -> Double { return sqrt(Double(lhs*lhs-rhs*rhs)) }
This way, the compiler knows which operator needs to be executed first. In case no precedence is specified, it defaults to DefaultPrecedence
, which is higher than TernaryPrecedence
.
This brings us to the conclusion of what operators are in Swift and how you can create your own. Although they are pretty simple to use, understanding them is important because they are one of the most foundational constructs of any programming language.
You should be familiar with basic operators, as you will be using them frequently in your code, and, when it comes to custom operators, define them only if the symbol’s original meaning makes sense for you.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowReact Native’s New Architecture offers significant performance advantages. In this article, you’ll explore synchronous and asynchronous rendering in React Native through practical use cases.
Build scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.