Structures and classes are the building blocks of flexible constructs, helping developers decide how to store data and model behavior in their programs. Classes within Swift are often seen as a blueprint for creating objects.
With the ability to store values by defining properties and adding functionality through creating methods, classes and structs’ shared features can often be used interchangeably in Swift. However, they both have differences and uniqueness, bringing the flexibility to developers to use them where they deem best.
We’ll review the similarities and differences between classes and structs as well as reviewing how they function within code.
The similarities between classes and structs in Swift offer interchangeability and flexibility. For instance, as mentioned previously, both classes and structs can define properties to store values, providing different options for storing data and modeling behavior in the code.
Other similarities include:
init()
keywordextension
keywordClasses, however, have additional capabilities that differentiate them from structs. Classes can inherit all properties, behaviors, and methods from another class, as well as add extra capabilities to what’s inherited
The other difference is type casting, which enables developers to check and interpret a class instance type at runtime.
The syntax for defining classes and structs in Swift are also similar. To define a class or struct in Swift, use the keyword class
or struct
followed by the name of the class or struct with curly braces.
As a note, ensure that classes and structs’ names in Swift follow the PascalCase naming convention.
In our example, let’s create a class and struct with the name User
:
class User { ... }
struct User { ... }
We can now add class and struct definitions to our code.
When we create our class and add class definitions, we can either provide default values, make the definitions optional, or create our own initializer.
In Swift, every class must have an initializer. If a class has subclasses, the initializer assures the compiler the subclasses inherit or implement the same initializer. This enables us to define class definitions.
For example, we can create a custom initializer in the code below by defining firstName
as String
to initialize and assign firstName
some values:
class User { var firstName: String var lastName: String var gender: String init(firstName: String, lastName: String, gender: String) { self.firstName = firstName self.lastName = lastName self.gender = gender } }
Note that in self.firstName = firstName
, self.firstName
refers to the firstName
we defined in our var firstName: String
class. self
refers to the current instance of User
.
When the User
class’s properties have default values, the User
class automatically implements a default initializer, creating a new instance with its properties set to their default values.
For a class definition with default values, we can add the following:
class User { var firstName = "Ejiro" var lastName = "Asiuwhu" var gender = "male" }
If we are unsure whether we want a variable to hold a value or assign a value later, we can make the variable optional. For a class definition with optional values, we can add the following:
class NewUser { var firstName: String? var lastName: String? var age: Int? }
There is only one way to define a struct in Swift:
struct User { var firstName = "Ejiro" var lastName = "Asiuwhu" var gender = "male" }
Class instances in Swift are known as objects. To use the User
class we created previously, we must create an instance:
class User { // class definition var firstName: String var lastName: String var gender: String var age: Int // creating our initilizer init(firstName: String, lastName: String, gender: String, age: Int) { self.firstName = firstName self.lastName = lastName self.gender = gender self.age = age } } // creating a class instance let person:User = User(firstName: "Ejiro", lastName: "Asiuwhu", gender: "male", age: 45)
It is worth noting that Swift class instances are mutable objects, while struct’s instances are immutable values. Because classes are a reference type, any changes made to a variable assigned to a class instance affect the original class, making it mutable.
On the other hand, because structs are a value type, any changes made to a variable assigned to a struct’s instance affect the original struct, making its value immutable.
When we need to access a class’s data, we can use the dot notation. For instance, to access the age
property of our User
class we created in our previous example, we can add the following:
// creating a class instance let person:User = User(firstName: "Ejiro", lastName: "Asiuwhu", gender: "male", age: 45) person.age // expected output: 45
Aside from accessing data, we can also use the dot notation syntax to set values to variable properties, allowing us to add additional data:
// creating a class instance let person:User = User(firstName: "Ejiro", lastName: "Asiuwhu", gender: "male", age: 45) person.age = 78 // expected output: 78
Both Swift classes and structs can define methods to provide functionality. By using the func
keyword to create a method in our User
class, we can add getDetails()
to access information like firstName
, lastName
, age
, and gender
:
class User { // class definition var firstName: String var lastName: String var gender: String var age: Int // creating our initilizer init(firstName: String, lastName: String, gender: String, age: Int) { self.firstName = firstName self.lastName = lastName self.gender = gender self.age = age } // methods in Swift classes func getDetails() { print("\(firstName) \(lastName) is a \(age) year old \(gender)") } // creating a class instance let person:User = User(firstName: "Ejiro", lastName: "Asiuwhu", gender: "male", age: 45) // the me person.getDetails() // expected output: Ejiro Asiuwhu is a 45 year old male
Notice how the newly created getDetails()
method is now available in our class instance. We can access the method using the dot notation on the let person:User = User
instance, followed by parentheses that call func
.
Similarly, we can also define methods in structs with dot notation as well to provide functionality:
struct User { var firstName: String var lastName: String var gender: String var age: Int func getDetails() { print("\(firstName) \(lastName) is a \(age) year old \(gender)") } } let person:User = User(firstName: "Ejiro", lastName: "Asiuwhu", gender: "male", age: 45) person.getDetails() // expected output: Ejiro Asiuwhu is a 45 year old male
Inheritance is a fundamental feature in classes that differentiates them from structs. Understanding how inheritance works is important when deciding whether to use a class or struct when writing Swift.
Subclassing allows us to inherit from one class to another, meaning a class (designated as a subclass) accesses all data, such as properties and methods, from another class (designated as a superclass).
To begin subclassing, we must define our superclass, and then base a new subclass on the existing superclass.
Subclassing doesn’t limit us either because we can add more functionality and properties to our subclass regardless of what we inherit.
To understand how inheritance works in Swift classes, let’s reuse our User
class as a superclass and create a subclass called Admin
to inherit the User
properties:
class User { // class definition var firstName: String var lastName: String var gender: String var age: Int // creating our initilizer init(firstName: String, lastName: String, gender: String, age: Int) { self.firstName = firstName self.lastName = lastName self.gender = gender self.age = age } } class Admin: User { var authorize: Bool? } var admin = Admin(firstName: "Ejiro", lastName: "Asiuwhu", gender: "male", age: 45) admin.authorize = true; print(admin.authorize) // expected output: true
Notice how we refine the Admin
subclass by adding more properties other than the one inherited from the User
superclass.
A fundamental feature that sets structs and classes apart is that structs are value types and classes are reference types.
When creating a struct and assigning it to a variable, value is copied because it is a value type. By setting the values of the point2
struct to be the value of the point1
struct, we are creating a separate copy of each variable.
So, when the values of point1
are changed, it doesn’t affect the values of point2
:
struct Coordinates { var lat: Double var lng: Double } var point1:Coordinates = Coordinates(lat: 5.519, lng: 5.7599) // here, we are setting the values of point2 to be the value of point1 var point2:Coordinates = point1 point2.lat = 6.45 point2.lng = 8.211 print(point2) // expected output: Coordinates(lat: 6.45, lng: 8.211) print(point1) // expected output: Coordinates(lat: 5.519, lng: 5.7599)
But when classes are assigned to a variable, it references the existing instance rather than copying it:
class User { var firstName: String var lastName: String var gender: String var age: Int init(firstName: String, lastName: String, gender: String, age: Int) { self.firstName = firstName self.lastName = lastName self.gender = gender self.age = age } } var user1:User = User(firstName: "Ejiro", lastName: "Asiuwhu", gender: "male", age: 29) // user2 now holds the same value as user1 var user2:User = user1 user1.age = 30 print(user1.age) // expected output: 30 print(user2.age) // expected output: 30
Notice the difference between value and reference types here: when a value changes in a reference type, all the referenced variables also change.
As we see in our class above, user1.age
and user2.age
are now the same value. This is because user1
is not just a copy of user2
, but rather user1
is user2
.
When we store a class, we are storing its value in memory and a variable that points to a class is only holding a reference to the class.
When we added var user2:User = user1
for our class, we are telling user2
to reference user1
, making all the data in both variables in sync. If we change one of them, the other changes.
The apple official documentation largely recommends that users should use structs by default. This is mostly because structs are much safer and bug-free, especially in a multithreaded environment. Structs are also preferable if they are relatively small and copyable because copying structs is safer than having multiple references to the same instance.
When choosing between structs and classes, it’s important to remember the key differences:
Classes and structs provide flexibility when working in Swift. While they are often interchangeable, their slightly different capabilities provide developers the choices they need.
Feel free to drop a comment to let me know what you thought of this article. You can also find me on Twitter and GitHub. Thank you for reading!
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>
Would you be interested in joining LogRocket's developer community?
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 nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.