Swift Structures

Marius Pascui
8 min readApr 28, 2022

Two weeks ago I’ve started learning Swift in my spare time and it has been an absolute joy. I wanted to learn programming in the past, I’ve watched Udemy tutorials, freecodecamp, you name it. But all of the other programming languages I was trying to learn (Python, JavaScript, PHP, etc) didn’t give me the feeling that Swift does. While I am aware of the fact that I am just scratching the surface, I feel confident that this is a programming language that I will be able to master sooner rather than later (or never, cough C# cough). So after learning the absolute basics about variable, constant, and Xcode layout it’s time to get a little bit more serious and talk about Swift structures.

Definition:

A structure is a named group of one or more properties that make up a type. Properties represent the information about an instance of the structure.

struct StructName {
// structure definition
}
  • struct — keyword used to define a structure
  • StructName — the name of the structure

Struct Instances

// This is the declaration for Person structure or a Person type
struct Person {
var name: String
}
//This is an instance of the Person typelet firstPerson = Person(name: "Jaime")

Initializers

An initializer is similar to a funtion that returns a new instance of the type.

Example of initializers

var string = String() // ""
var integer = Int() // 0
var bool = Bool() // false

When defining a new type, we have to consider how we will create new instances.

Default Values

One approach is to define default property values in our type definition. This is useful when defining objects that have a consistent default state.
If we provide default values for all instance properties of a structure, the Swift compiler will generate a default initializer for us.

In this example:

struct Odometer {
var count: Int = 0
}
let odometer = Odometer() // an instance of the Odometer type
print (odometer.count) // the Console Output will be: 0

after declaring struct Odometer, we give the variable count a default property value equal to 0, which means that all new instances of Odometer will be created with that default value using the Odometer() initializer.

Memberwise Initializers

The other approach to creating a new instance is by using memberwise initializers, which are special initializers created by Swift when we define a new structure without set initial values.

Memberwise initializers allows us to set an initial value for each property of the new instance.

Example:

struct Odometer {
var count: Int // in this case, the count variable has no default value
}
let odometer1 = Odometer(count: 1000)
print(odometer1.count) // the Console Output will be: 1000
let odometer2 = Odometer(count: 2022)
print(odometer2.count) // the Console Output will be: 2022

To clarify, unlike in the default values approach, in the example given above the count property has no default value in the declaration of the struct Odometer so we must give it a value when creating a new instance.

Custom Initializers

In order to customize the initialization process we can define our own initializers, and we need to make sure that, just like in the case of default and memberwise intializers, all properties have a set initial value.

Here’s an example:

struct Temperature {
var celsius: Double
init(celsius: Double){
self.celsius = celsius
}
init(fahrenheit: Double) {
celsius = (fahrenheit - 32) / 1.8
}
}
let currentTemperature = Temperature(celsius: 18.5)
let boiling = Temperature(fahrenheit: 212.0)
print(currentTemperature.celsius) // Console output: 18.5
print(boiling.celsius) // Console output: 100.0

In the case of custom initializers we must provide our own memberwise initializers and default initializers as Swift no longer provides them for us and just like in the example above, multiple custom initializers can be added (celsius and Fahrenheit).

Instance Methods

Instance methods are functions that can be called on specific instances of a type and they provide ways to access and modify properties of the structure.

In the example below, we have a Size struct with an instance method area( ) that calculates the area of a specific instance by multiplying its width and height.

struct Size {
var width: Double
var height: Double
func area() -> Double {
width * height
}
}
let someSize = Size(width: 10.0, height: 5.5)
let area = someSize.area() // The "area" constant is assigned a value of 55.0

Here’s a paragraph from the Swift Fundamentals book that helps with understanding the example above:

The someSize instance of the Size type, and width and height are its properties. The area( ) is an instance method that can be called on all instances of the Size type.

Mutating Methods

When we want to update the property values of a structure within an instance method all we have to do is add the mutating keyword before the function.

In the example below, a simple structure stores mileage data about a specific Car object. Here is what we need our mileage counter for:

  • Store the mileage count to be displayed on an odometer
  • Increment the mileage count to update the mileage when the car drives
  • Potentially reset the mileage count if the car drives beyond the number of miles that can be displayed on the odometer
struct Odometer {
var count: Int = 0 // Assigns a default value to the 'count' property

mutating func increment() {
count += 1
}

mutating func increment(by amount: Int) {
count += amount
}
mutating func reset() {
count = 0
}
}
var odometer = Odometer() // odometer.count defaults to 0
odometer.increment() // odometer.count is incremented by 1
odometer.increment(by: 15) // odometer.count is increment to 16
odometer.reset() // odometer.count is reset to 0

Computed Properties

Swift has a feature that allows a property to perform logic that returns a calculated value.

Example:

struct Temperature {
var celsius: Double
var fahrenheit: Double
var kevlin: Double
}
let temperature = Temperature(celsius: 0.0, fahrenheit: 32.0, kevin: 273.5)

Instead of having to calculate each temperature and pass all those values as parameters when writing code to initialize a Temperature object, we can instead do this:

struct Temperature {
var celsius: Double
var fahrenheit: Double {
celsius * 1.8 + 32
}
var kelvin: Double {
celsius + 273.15
}
}
let temperature = Temperature(celsius: 0.0)
print(temperature.fahrenheit) // Console Output: 32.0
print(temperature.kelvin) // Console Output: 273.15

The logic contained in a computed property will be executed each time the property is accessed, so the returned value will always be up to date.

Property Observers

When we want to observe a property and respond to the changes in the property’s value we can use property observers which are called every time a property’s values is set, even if the new value is the same as the property’s current value.

We can add property observers in the following places:

  • Stored properties that we define
  • Stored properties that we inherit
  • Computed properties that we inherit

There are two observer closures, or blocks of code, that can we can use on any given property:

  • willSet — is called just before the value is stored
  • didSet — is called immediately after the new value is stored

With a willSet block we will automatically have access to a new constant named newValue that will store the modified value of the property that is was set to. After the property’s value has been updated, didSet will be called, and we can access the previous property values using oldValue, which is also automatically created.

Example:

struct StepCounter {
var totalSteps: Int = 0 {
willSet {
print("About to set totalSteps to \\(newValue)")
}
didSet {
if totalSteps > oldValue {
print("Added \\(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
/*Console output:
About to set totalSteps to 200
Added 200 steps
*/
stepCounter.totalSteps = 360/*Console output:
About to set totalSteps to 360
Added 160 steps
*/

Type Properties and Methods

Type properties are useful for defining values that are universal to all instances of a particular type, such as a constant property that all instances can use, or a variable property that stores a value that’s global to all instances of that type.

A more condensed definition would be: Type properties are useful when a property is related to the type, but not a characteristic of an instance itself.

The following example defines a Temperature structure that has a static property named boilingPoint, which is a constant value for all Temperature instances:

struct Temperature {
static let boilingPoint = 100
}
let boilingPoint = Temperature.boilingPoint

Copying

When we assign a structure to a variable or pass an instance as a parameter into a function, the values are copied, which means that separate variables are separate instances of the value and changing one value will not change the other.

Example:

var someSize = Size(width: 200, height: 500)
var anotherSize = someSize
someSize.width = 500print(someSize.width) // Console Output: 500
print(anotherSize.width) // Console Output: 200

As we can see, the width property of someSize changed to a value of 500, but the width property of anotherSize stayed the same. That is because when we set anotherSize to equal someSize we’ve created a copy of someSize and the copy’s width property did not change when the original width property was changed.

Self

Every instance of a type has an implicit property called self, which is equivalent to the instance itself. We can use the selfproperty to refer to the current instance within its own instance methods.

In practice, we don’t need to write self in our code very often. If we don’t explicitly write self, Swift assumes that we are referring to a property or method of the current instance whenever you use a known property or method name within a method.

However, the use of self is required within initializers that have parameter names which match the property names. In this situation, the parameter name takes precedence, and it becomes necessary to refer to the property in a more qualified way, so we use the self property to distinguish between the parameter name and the property name.

Example:

struct Temperature {
var celsius: Double
init(celsius: Double) {
self.celsius = celsius // the value of the property named "celsius" (the variable) is assigned to the parameter named "celsius"
}
}

Variable Properties

Variable properties provide a convenient way to create new data from old data.

For example, we can create multiple instance of a Car if the Car structure has variable properties like so:

struct Car {
var make: String
var year: Int
var topSpeed: Int
}
var firstCar = Car(make: "Toyota", year: 2021, topSpeed: 230)
var secondCar = firstCar
secondCar.make = "Mazda" // Console Output: Car(make: "Mazda", year: 2021, topSpeed: 230)

However, if we declare the first instance as a constant the code written above will receive a compiler error:

let firstCar = Car(make: "Toyota", year: 2021, topSpeed: 230)
var secondCar = firstCar //Compiler error!

As a general rule, we should use let whenever possible to define an instance of a structure, use var if the instance needs to be mutated, and use var when defining the properties of a structure.

Please keep in mind that this is by no means a tutorial as I am still very much a noobie at Swift. These are just some short examples that may help other beginners or may spark interest in people who have never been interested in Swift before. I’ll leave more resources and references that I’ve used when learning Swift Structures below this post.

What do you think about Swift structures? Did you find this post helpful? Let me know in the comments section. Until next week, keep on learning 🎓.

https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html

https://books.apple.com/us/book/develop-in-swift-fundamentals/id1511184145

--

--