Published on

Constants and Variables in Swift

Authors

Unveiling the Power of Swift: A Journey into Constants and Variables

Welcome to the dynamic world of Swift programming, where the mastery of constants and variables lays the foundation for building powerful and flexible applications on iOS, macOS, watchOS, and tvOS. As you embark on this journey, you'll discover the significance of these fundamental building blocks and how they shape the behavior of your code.

Swift, like most programming languages, utilizes variables and constants.

  • Variables are for storing values that can change.
  • Constants hold those values that shouldn't change.

If you're acquainted with JavaScript, you'll know that constants are declared using const and variables can be declared using either var or let.

In contrast, Swift has a slightly different approach:

  • Declare a constant with let
  • Declare a variable with var

Variables

A variable in Swift provides you with a named storage place that allows your code to manipulate its stored value. A variable's name (also referred to as an identifier) is associated with a value that we can change during program execution.

Variables.swift
var intValue: Int = 10
intValue = 20 // It's permissible. As intValue is a variable, we can change its value.```

In the above example, intValue is a variable of type Int, initially assigned the value 10. Later, we can modify its value to 20 or any other integer value.

Constants

Unlike a variable, a constant, once declared in Swift, maintains its value throughout the execution and cannot be altered after its initial set-up.

Constants.swift
let constantValue: Int = 100
constantValue = 200 // It's not permissible. As constantValue is a constant, we cannot change its value.

Above, constantValue is a constant that's been assigned the value 100. If you attempt to modify the constantValue, a compile-time error will occur because altering the value of a constant is prohibited in Swift.

Type Inference

Swift employs type inference, a feature that allows the compiler to deduce the type of an expression automatically. Because of this, when you're declaring constants or variables, you don't have to explicitly indicate the type, as long as it can be inferred from the initial value.

TypeInference.swift
let inferredDouble = 3.14159 // Inferred as double
var inferredString = "Hello" // Inferred as String

In the examples above, inferredDouble is inferred as Double and inferredString as String automatically, eliminating the need for explicit type declarations.

Key Differences

The main differences between constants and variables can be boiled down to two essential points:

  1. Mutability: The crucial difference lies in their respective natures. Variables are mutable, meaning they can amend their values at any point, whereas constants are immutable—once their value is set, it doesn't change
  2. Performance: Constants can be more performance efficient. In programming, the predictability brought by constants allows the compiler to make specific optimizations leading to better-performing code.

Swift encourages you to use constants (let) wherever the value doesn't need to be changed. This practice enhances safety by preventing inadvertent value modification. Variables (var), on the other hand, should be used for values that may need to be altered.

ConstantsAndVariables.swift
let pi = 3.14159 // The value of pi never changes, so using `let` is appropriate.

var username = "Mario" // The username might change, so we use `var`.
username = "Swift Mario" // You can manipulate the value since it's a variable.

Diving Deeper

Scope and Lifetime of Constants and Variables

Every variable or constant in Swift has a scope and a lifetime. Scope implies where a variable or constant can be accessed or used in your code. A variable declared outside of all function bodies has a global scope and can be used anywhere in the code. However, a variable declared within a function has local scope and can only be used within that function. The lifetime of a variable or constant begins when it is declared and ends when it is no longer used and is reclaimed by the system. Understanding the scope and lifetime of variables and constants is crucial to avoid errors and memory leaks.

In Other Words

So, in basic terms, every variable or constant in Swift has a scene— called scope — where it can make an appearance, and it also has a lifespan — called lifetime. Just think of your variable as a movie star; it can only be seen where the script (your program) allows it. So, if it's decided that our star should only be seen in a garden scene (a function), then it just can't suddenly turn up in a shopping mall (another function)! Also, just like our star, our variable is not immortal — it has a lifetime, starting when it's created and ending when the system sees it has no more use and politely shows it the exit. Keeping track of this is crucial to ensure your code doesn't trip over its own feet.

Type Safety in Swift

Swift is touted for being a type-safe language. It means the Swift language is very strict about the data types you use with each variable or constant. For instance, if you declare a variable as an Int, Swift won't allow you to later use that variable as a String This type-safety feature vastly minimizes the likelihood of unexpected behavior or crashes. It allows for most type checking errors to be caught at compile-time, before any problematic code is run.

In Other Words

Swift is known for its strict nature — once it decides something, it sticks to it. If you're telling Swift your variable should be an Int, Swift won't let you just randomly use it as a String. It's like telling someone they're going to be eating an apple and handing them a pretzel. That won't work, and Swift knows it. That's called type safety! It stops your code from going haywire and limits unexpected errors, which is always a good thing.

Value and Reference Types

In Swift, variables and constants can store values of value types (like structures and enumerations) and reference types (like classes). When you assign a value type to a variable, constant, or pass it to a function, Swift creates a new, separate copy of the value. In the case of reference types, Swift doesn't create a copy but instead passes a reference to the existing object. Hence, changes made from one reference point affect the same object from another reference point. Understanding the difference between value and reference types in Swift is crucial for managing mutable state and avoiding common programming mistakes.

Structs are value types. When you make a copy of a value type, it creates an entirely new object:

ValueType.swift
struct Cookie {
    var flavor: String
}

var myCookie = Cookie(flavor: "Chocolate Chip") // We have a cookie with Chocolate Chip flavor
var yourCookie = myCookie // The cookie gets copied for you

yourCookie.flavor = "Oatmeal Raisin" // You changed your cookie's flavor

print(myCookie.flavor) // Outputs: Chocolate Chip
print(yourCookie.flavor) // Outputs: Oatmeal Raisin

As you can see, changing yourCookie did not affect myCookie. This is because they are separate instances.

In contrast, classes in Swift are reference types. Let's consider Car as a class. This makes it a reference type in Swift:

Car.swift
class Car {
    var isCarOpen: Bool
    init(isCarOpen: Bool) {
        self.isCarOpen = isCarOpen
    }
}

Now, let's imagine you have your mainKey and spareKey:

Car.swift
class Car {
    var isCarOpen: Bool
    init(isCarOpen: Bool) {
        self.isCarOpen = isCarOpen
    }
}

var mainKey = Car(isCarOpen: false) // The car is initially locked
var spareKey = mainKey // The spare key refers to the same car

You can use the spare key to open the car:

Car.swift
class Car {
    var isCarOpen: Bool
    init(isCarOpen: Bool) {
        self.isCarOpen = isCarOpen
    }
}

var mainKey = Car(isCarOpen: false) // The car is initially locked
var spareKey = mainKey // The spare key refers to the same car

spareKey.isCarOpen = true // The car is now opened using the spare key

Since both mainKey and spareKey refer to the same instance (your car), any action performed on the car via the spareKey will reflect when accessed via the mainKey.

Car.swift
class Car {
    var isCarOpen: Bool
    init(isCarOpen: Bool) {
        self.isCarOpen = isCarOpen
    }
}

var mainKey = Car(isCarOpen: false) // The car is initially locked
var spareKey = mainKey // The spare key refers to the same car

spareKey.isCarOpen = true // The car is now opened using the spare key

print(mainKey.isCarOpen) // Outputs: true
print(spareKey.isCarOpen) // Outputs: true

This exactly mimics the behavior of reference types. Both mainKey and spareKey point to the same instance of the Car class, so changes made via spareKey also affect mainKey.


NOTE In this example, we made a simplified demonstration to illustrate the concept of reference types. However, in a more realistic or complex program, it would probably be clearer to have a Car class with a carKey property that refers to a CarKey class. That way, different keys could have different properties (like whether they're a main key or a spare), but all could still interact with the Car instance. This would help us model more complex real-world relationships more accurately in our code. This distinction further emphasizes the flexibility and control we can have over data structures when understanding and utilizing differences between value and reference types.


In Other Words

In Swift, variables and constants can hold values described as value types and reference types. Think of a value type like a firsthand story — every time you pass it around, a fresh copy is made. But a reference type is more like a game of 'Telephone'. Instead of creating a new story, everyone just refers back to the original. So, any tweaks that happen affect the same original storyline. It's handy to know which is which to keep your code neat and your variables behaving.

Type Conversion

Swift does not implicitly convert types. This means you must perform explicit type conversion. Here is an example showing how to convert an Int to a Double:

TypeConversion.swift
let intVal: Int = 10
let doubleVal: Double = Double(intVal) // the value of doubleVal is now 10.0

In this example, we have successfully converted an Int to a Double. We did this by using the Double() initializer, passing in our integer.


NOTE There is also the process of Type Casting which is different from Type Conversion.


Optionals

An important Swift feature to discuss in conjunction with variables and constants is the concept of optionals. Optionals are used to represent the absence of a value and are in essence a type-safe way to say a variable can be nil. When declaring a variable or constant, you can use a question mark ? to denote an optional type, meaning it can hold either a value or nil. There is also the concept of optional binding and optional chaining that deal with safely handling and unwrapping these optional values. Optionals, though initially might seem complex, play a key role in Swift's app safety by preventing unexpected crashes due to nil values.

In Other Words

Consider a jar of jam here as a variable in Swift. When the jar is full of jam, it represents that the variable contains a value. However, at times you just run out of jam, leaving the jar empty. In terms of programming, this is represented as nil, the absence of value. Now, if we assume, not every jar in your pantry is guaranteed to be full, how do you represent this in Swift? That's when we bring in the 'optionals'! In Swift, an optional is like a jar that could either be full of jam ('value') or completely empty ('nil'). By defining our jar of jam as optional, we are accepting that it isn't always guaranteed to have jam inside - sometimes, it might be empty.

Optionals.swift
var jarOfJam: String? // Declaring an Optional, our jar could have jam or not!

jarOfJam = "Strawberry" // Filling the jar with delicious Strawberry jam
jarOfJam = nil // Uh-oh, we just ran out of jam. Now it's empty.

When you want to use the value (jam) inside the optional (jar), you first need to check if there's jam inside. This process is known as unwrapping. You'd hate to plan on making a PB&J sandwich only to find out last minute there's actually no jam in the jar, right?

Swift gives us optional binding or optional chaining - basically, clever, safe ways to check our jar for jam before we start spreading it on our sandwich, thus protecting our code from crashes (or our metaphorical sandwich session from heartbreaking disappointment!).

Wrapping Up

I hope that this guide has clarified the workings of constants and variables in Swift for you. Essential to remember is that variables are mutable (their values can change), while constants are immutable (their values are set for their lifetime).

You can read more about the basics in Apple's documentation