Ejiro Asiuwhu Software developer with industry experience in building scalable and performant applications that run on the web and smartphones with cutting-edge technology.

Introduction to classes and structs in Swift

6 min read 1833

Introduction Classes Structs Swift

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.

Swift classes and structs similarities and differences overview

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:

  • Initializers that set up initial state values by using the init() keyword
  • The ability to define subscripts, providing quick access to a sequence’s value, collection, or list
  • The ability to extended a class or struct using the extension keyword
  • Protocol conformance

Classes, 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.

Class and struct syntax similarities in Swift

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:

We made a custom demo for .
No really. Click here to check it out.

class User {
  ...
}
struct User {
 ...
}

We can now add class and struct definitions to our code.

Class definition

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?
}

Struct definition

There is only one way to define a struct in Swift:

struct User {
  var  firstName = "Ejiro"
  var  lastName = "Asiuwhu"
  var  gender = "male"
}

Creating Swift class instances

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.

Accessing properties of class instances

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

Creating Swift methods in classes and structs

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

Swift class differences

Class inheritance

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.

Values and references

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.

When to use classes vs. structs in Swift

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 are reference types, and structs are value types
  • If class inheritance is not needed, structs are faster and more memory efficient
  • Use structs for unique copies of an object with independent states
  • Use structs when working with a few, relatively simple data values
  • Use classes to access Objective-C runtime
  • Use classes to control an object’s identity
  • Use structs when there is no need to control an object’s identity

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!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Ejiro Asiuwhu Software developer with industry experience in building scalable and performant applications that run on the web and smartphones with cutting-edge technology.

Testing accessibility with Storybook

One big challenge when building a component library is prioritizing accessibility. Accessibility is usually seen as one of those “nice-to-have” features, and unfortunately, we’re...
Laura Carballo
4 min read

Leave a Reply