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 Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
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>

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the November 5th issue.

A senior developer discusses how developer elitism breeds contempt and over-reliance on AI, and how you can avoid it in your own workplace.

Examine AgentKit, Open AI’s new tool for building agents. Conduct a side-by-side comparison with n8n by building AI agents with each tool.

AI agents powered by MCP are redefining interfaces, shifting from clicks to intelligent, context-aware conversations.
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 now