Back to blog

Structs In Swift Explained


Aasif Khan
By Aasif Khan | December 22, 2021 5:34 pm  | 4-min read

In Swift, you use structs to wrap and store complex data types. And they’re awesome! In this tutorial, you’ll learn how to use structs. We’ll also dive into the differences between structs and classes, and when you should use one or the other.

What’s A Struct?

You use structs, or “structures”, in Swift to wrap and store complex data types. Let’s dive right in with an example:

struct Receipt
{
var ID:Int = 0
var productName:String = “”
var productPrice:Double = 0.0
var isPaid:Bool = false
}

In the above code, a struct named Receipt is declared. It has 4 properties, such as productName of type String. The above code is similar to how classes are declared in Swift.

See how the properties ID, productName, productPrice and isPaid have an initial value? They are the default values for newly created instances of Receipt. We can change these values later on, or use the synthesized default initializer to set the properties when creating a new instance.

You can now use Receipt like in the example below. This will create a new instance of Receipt, using the memberwise initializer, and print out a few of the struct’s values.

let receipt = Receipt(ID: 3, productName: “Tea”, productPrice: 1.49, isPaid: true)
print(“You bought a \(receipt.productName) for $ \(receipt.productPrice)”)

You can also create a Receipt object, keeping the initial values as they are, and changing the properties directly. Like this:

var receipt = Receipt()
receipt.productPrice = 2.99
receipt.productName = “Coffee”

What we’ve essentially done here with the above code, is define or “wrap” a complex data type, called Receipt. The Receipt type encapsulates what we want to like to associate with an actual purchase receipt, such as a price and whether a purchase has been made successfully.

Use structures when you want to define a complex data type in Swift. Think of tangible data types, such as Circle, User, Tweet and Article. At the end of this tutorial, we’ll discuss how structs and classes are similar – but different.

Structs automatically get a so-called memberwise initializer, which is similar to a default initializer, even if you don’t declare default values for the its properties. Default and memberwise initializers are only added if you don’t provide any init() functions yourself. In Swift 5.1, you can use the memberwise initializer in conjunction with default property values, and leave out initializer parameters to set them to their default value. Learn more about initializers here: Initializers & init() Explained In Swift

Structs: Properties And Functions

Just like classes, structs can define and use a bunch of Swift’s default building blocks:

  • They can define properties to store values, like we’ve done before
  • They can define functions (called methods) to define functionality
  • They can define computed properties; properties with a dynamic value

We’ve already looked at how structs can define properties. They can also define functions, officially called methods. Like this:

struct Receipt
{
var ID:Int = 0
var productName:String = “”
var productPrice:Double = 0.0
var isPaid:Bool = false

mutating func makePurchase(name: String, price: Double)
{
productName = name
productPrice = price

isPaid = true
}
}

The makePurchase(name:price:) function can now be used to change the properties of the Receipt object, to “simulate” making a purchase. Like this:

var receipt = Receipt()
receipt.ID = 99

receipt.makePurchase(name: “Beer”, price: “4.99”)
print(“Bought a \(receipt.productName) for $ \(receipt.productPrice)”)

Why is the makePurchase(…) function marked with the mutating keyword? That’s because the function changes the properties of the current instance. If you want to create a function that can mutate (or change) the properties of a struct, you have to explicitly declare the function with mutating. It’s a safeguard to prevent you from accidentally changing the object’s values.

Structs can also declare computed properties. A computed property is, like its name implies, a property that changes its value dynamically based on a computation. Here, check out this example:

struct Circle
{
var radius:Double

var circumference:Double {
return 2 * .pi * radius
}
}

let balloon = Circle(radius: 30)
print(balloon.circumference)

The above code defines a struct named Circle. It has one property radius of type Double. So far so good! The circumference property is computed. Its value is computed based on the result of the expression 2 * .pi * radius, or 2πr, which calculates the circumference of a circle based on its radius.

In the last two lines of the above example, you can see how we’re creating an instance of Circle with a radius of 30. Finally, we’re calling the circumference computed property as if it’s a normal stored property. Under the hood, the 2 * .pi * radius computation is invoked, and its result is returned as the value for the property. Neat!

Finally, structs can also do a bunch of things that classes can do too:

  • They can provide subscripts and use subscript syntax
  • They can define initializer functions, and get automatically synthesized initializers, such as the memberwise initializer
  • They can be extended, with extensions, but cannot be subclassed
  • They can conform to protocols, which is crucial for using structs’ full potential

Can classes do things that structs cannot? Yes!

  • Classes can subclass other classes, and inherit their functions and properties
  • Classes can use type casting to inspect and treat their types
  • Classes can declare a deinitializer with deinit()
  • Classes are reference types, whereas structs are value types

Let’s move on, to learn more about value types!

Structs Are Value Types

An important distinction that we need to make here, when discussing structs, is that they are value types. In short, value types keep a copy of their data, whereas reference type share a copy of the data.

Let’s take a look at an example of what that means for structs. First, we’re creating a simple struct named Cat.

struct Cat
{
var name:String = “”
}

Then, we create an instance of Cat. Like this:

var snuffles = Cat()
snuffles.name = “Snuffles”

Next, we’re creating a copy of snuffles variable. Like this:

var felix = snuffles
print(felix.name)
// Outputs: Snuffles

So far so good, right? We’ve assigned snuffles to felix, and print out the value of felix.name. Because we haven’t changed the name property of felix yet, it’ll just output the string “Snuffles”.

The big question is: what happens when we change the name property of felix? Will it change felix or snuffles or both? Let’s find out!

felix.name = “Felix”
print(felix.name) // Outputs: Felix
print(snuffles.name) // Outputs: Snuffles

Hey, that’s interesting! Both felix and snuffles each refer to a different instance of a Cat object, even though we created the felix object by copying snuffles.

Because Cat is a struct, and a struct is a value type, each variable keeps a copy of their own data. Comparing a struct with a class – a class will share a copy of the data, and merely copies the reference to that data.

Why don’t you see for yourself? Give the Swift sandbox below a try. First, try out the example. Then, change struct to class and see how the output changes. A class shares references, which means that changing felix will change snuffles too!

struct Cat {
var name:String = “”
}

var snuffles = Cat()
snuffles.name = “Snuffles”

var felix = snuffles

felix.name = “Felix”

print(felix.name)
print(snuffles.name)

When To Use: Structs vs. Classes

The differences between structs and classes will make you wonder: when should you use structs, and when should you use classes?

The official Swift documenation recommends you to use structs by default, and use classes if you need specific features that only classes have. Structs have stored and computed properties, functions, can adopt protocols, and define subscripts. That’s adequate for simple use cases, such as “wrapping” a complex type like Article or Tweet.

Why use structs by default? Because it makes it easier to reason about your code. A struct can’t be referenced by some other part of your code, like classes can, so you can be certain that changing it won’t affect another part of your app unless you explicitly code it like that. You could say that structs are “contained” locally, which makes reasoning about state changes much easier.

Imagine we’re building a Twitter app. The tweets are passed to the app via a web-based API. We’re defining the Tweet type as a struct, and use it to display the tweet’s number of retweets. When the retweets are displayed, we can be certain that no other code can change the state of the Tweet value. Only the current, local context can make changes to the Tweet. Whenever the data changes, it will need to do so explicitly, so we can clearly see what code affects the Tweet value. If we would have used a class, we would have no guarantees about the state of Tweet.

The opposite is also true. Imagine we’re changing the number of retweets locally in the app. We can be certain that our change remains local, and isn’t persisted in the database, because the current context is the only one able to change the Tweet value. It’s impossible to accidentally change the Tweet in the database, unless we explicitly propagate the changes.

If you want to learn more about the differences between structs and classes, check out this tutorial.

Further Reading

In this tutorial, we’ve discussed what structs are and how you can use them. A main take-away is, hopefully, that structs are best used as default types for complex data. We’ve also discussed how structs and classes are similar but different, and how you can make your Swift code better by using the right data type. Great!


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts