Back to blog

Struct vs. Class in Swift Explained


Aasif Khan
By Aasif Khan | Last Updated on June 4th, 2024 8:27 am | 4-min read

Are you developing your mobile app and wondering what’s the difference between classes vs. structs? They’re so alike! It’s best to use structs by default, but why? And when should you use classes, then?

In this tutorial, we’re going to take a look into structs vs. classes. When do you use a class, and when do you use a struct? What’s the difference between a struct and a class, and how does that affect practical iOS development?

Structs are a fundamental aspect of Swift, and they can help to make your code more reusable, more flexible, and less tightly coupled. And last but not least, they help you sharpen your skills as an app developer!

Describe your app idea
and AI will build your App

At A Glance: Classes vs. Structs

First, let’s take a look at what features classes and structs have in common:

  • Both structs and classes can define properties to store values, and they can define functions
  • They can define subscripts to provide access to values with subscript syntax
  • They can define initializers to set up their initial state, with init()
  • They can be extended with extension (this is important!)
  • They can conform to protocols, for example to support Protocol Oriented Programming
  • They can work with generics to provide flexible and reusable types

Classes support a few more capabilities that structs don’t have:

  • Classes can inherit from another class, like you inherit from UIViewController to create your own view controller subclass
  • Classes can be deinitialized, i.e. you can invoke a deinit() function before the class is destroyed
  • Classes are reference types and structs are value types

That last point is important: classes are reference types, and structs are value types. Here’s what’s what:

  • Value Type: When you copy a value type (i.e., when it’s assigned, initialized or passed into a function), each instance keeps a unique copy of the data. If you change one instance, the other doesn’t change too.
  • Reference Type: When you copy a reference type, each instance shares the data. The reference itself is copied, but not the data it references. When you change one, the other changes too.

Beyond these foundational differences, the choice between classes and structs often boils down to the specific requirements of your application. For instance, if you’re working on a project that heavily relies on shared state or needs to maintain a single source of truth, classes might be more appropriate due to their reference nature.

On the other hand, if you’re aiming for immutability or want to ensure that data remains consistent across multiple threads, structs, with their value semantics, become the go-to choice. It’s also worth noting that while classes provide the power of inheritance, it’s essential to use this feature judiciously to avoid complexities and maintain a clean codebase.

Let’s take a look at an example:

  • Reference Type: Bob has a phone number, written on a piece of paper. He shares it with Alice. Alice doesn’t write down the phone number for herself, but instead remembers that Bob has it. When she needs the phone number, she uses Bob’s piece of paper. When Alice accidentally changes one digit of the phone number, Bob’s phone number changes too. Picture this as Bob and Alice both holding the piece of paper the phone number is written on.
  • Value Type: Bob has a phone number, and he gives it to Alice. Alice writes it down and now has her own copy. When she accidentally changes it, only her copy changes, and not the original phone number Bob has. Both Bob and Alice have their unique copy of the phone number.

In Swift, structs are value types whereas classes are reference types. When you copy a struct, you end up with two unique copies of the data. When you copy a class, you end up with two references to one instance of the data. It’s a crucial difference, and it affects your choice between classes or structs.

A smart shorthand to remember the difference, is that a value type copies the value, and a reference type copies the reference.

When Should You Use Structs?

It’s recommended to use struct in Swift by default. Structs are helpful in these scenarios, too:

  • Simple Data Types

    Use structs for simple data types. Think about database objects you want to pass around in your code, like NewsItem, Task or User. Since they’re so well-defined, and often don’t need to accommodate complex relationships between objects, it’s simpler to use structs.

  • Thread Safety

    In a multi-threaded environment, for instance with a database connection that’s opened in a different thread, structs are safer. They can be copied from one thread to another thread, without running the risk of a race condition or deadlock. Classes do not have this inherent safety, unless they’re deliberately made thread-safe.

  • Mostly Structs Scenario

    When the properties of a struct are mostly value types too, like String, it makes sense to wrap them in a struct instead of a class. It’s OK to use structs within class types, but pay extra attention if you use classes within struct types. Classes are reference types, so if you’re unaware that your struct references a shared class instance, and your struct gets copied, both structs share a reference to that class!

  • Don’t Need Inheritance

    Structs cannot inherit code from other structs. If you don’t need inheritance, it’s smarter to use structs (instead of classes). Structs can still conform to protocols, and they can be extended, so you still have a few options for creating flexible, composable code.

  • Predictable Data Manipulation

    With structs, data manipulation is more predictable. When you modify a struct, you’re working on a distinct copy, ensuring the original data remains unchanged. This is especially beneficial when handling data that shouldn’t be inadvertently altered.

  • Performance Benefits

    Structs can be more performant than classes, especially in situations where small data structures are frequently created and destroyed. Their allocation on the stack, as opposed to the heap for classes, ensures quicker access and deallocation times.

Using structs has a huge benefit: it’s easier to reason about data changes in your code. When a type is a struct, you can be certain that no other part of your code can hold on to a reference to that struct. Unless it’s explicitly coded, a struct cannot be changed by some other part of your code.

Remember that metaphor we used earlier, with Bob and Alice and the phone number? Imagine that the phone number is written down on a Note, a struct. When Bob passes a Note object to Alice, the object is copied, which creates two unique instances. When Alice changes the phone number, Bob’s copy does not change. This means Bob can more easily reason about his code, because he can be certain that his copy cannot be changed without him knowing. If there’s code that changes Bob’s copy, it’ll need to be explicit code.

Just so we’re on the same page, this is how you define and use a struct:

struct NewsItem {
var title: String = “”
var url: String = “”
}

var item = NewsItem()
item.title = “Struct vs. Class in Swift Explained”
item.url = “https://learnappmaking.com/struct-vs-class-swift-how-to/”

print(item.title)
// Output: Comparing Classes vs. Structs in Swift
As you can see, the syntax is effectively the same as for defining and using a class. Instead of class name { you write struct name { .

Want to learn more about structs? Check out this tutorial: Structs In Swift Explained

When Should You Use Classes?

It’s recommended to use a class if you need the specific features of a class. This is why working with structs is the default, and classes are a deliberate choice.

Classes have a few extra characteristics that structs don’t have:

  • Classes can inherit from another class, which you can’t do with structs. With classes, you can write class MyViewController : UIViewController to create a subclass of UIViewController. Structs can implement protocols, can be extended, and can work with generics, though!
  • Classes can be deinitialized, i.e. they can implement a deinit function. Also, you can make one or more references to the same class (i.e., classes are a reference type).
  • Classes come with the built-in notion of identity, because they’re reference types. With the identity operator === you can check if two references (variables, constants, properties, etc.) refer to the same object.

It’s important to note here that you need to use classes if you want to interop between Swift and Objective-C. If you need Objective-C interoperability, you’ll need to use classes. For example, if you want to use @objc and dynamic in a Realm data model, that model needs to be a class.

Next to reference vs. value types, inheritance is the most important difference between a class and a struct. With classes, you can clearly define a parent-child connection between subclass and superclass.

A few examples:

  • MyViewController inherits from UIViewController
  • MyTableViewController inherits from InfiniteTableViewController to adopt “infinite scrolling”, which in turn inherits from UITableViewController
  • Car and Bike both inherit from Vehicle, because they both use the same “basic” set of characteristics like numberOfWheels and speed.

In these last two examples lies a danger: you can end up with a whole bunch of inherited classes – top-to-bottom – that all “decorate” their superclass with different functionalities. Think about SuperCar and MuscleCar, that both inherit from Car, and from Vehicle. This gets tightly-coupled really fast.

It’s easy to get lost in which class inherits what, even though, at first sight, it makes sense to structure your classes like this. What if SuperCar inherits a function that it doesn’t need from Vehicle? What if you want to create a SuperBike, that’s similar to a SuperCar, but you can’t “inherit” or share those characteristics because they’re in different subclass-superclass hierarchies? In that case, it may make sense to use composition instead.

Based on what we’ve discussed about identity and references, we can assert that it’s best to use classes in these scenarios:

  • Copying Doesn’t Make Sense

    When copying or comparing instances doesn’t make sense, e.g. with Window or UIViewController. It doesn’t make sense to copy an app window, since there’s only one active at a time, and it often doesn’t make sense to copy a view controller either – you’d just create a new one.

  • External Effects or Data

    When the lifetime of an instance is tied to external effects, e.g. for DatabaseConnection or TemporaryFile. It doesn’t make sense to create two copies of a reference to a file on the disk, after all, they both reference the same data, and represent that data in code.

  • Intermediaries

    When instances are merely conduits for external states, e.g. for CGContext or PersistenceController. Sometimes you need a helper or wrapper class to get things done: an API or a reference to an online resource. In those cases the class is only a conduit, something that passes along information, and it doesn’t make sense to create a copy of that.

  • Dynamic Behavior

    When the behavior of an object needs to be changed at runtime or when leveraging polymorphism. Classes, with their ability to use dynamic dispatch, can adapt and change behavior, making them suitable for patterns like the Strategy pattern.

  • Lifecycle Observations

    In scenarios where you need to observe or react to the lifecycle events of an object. Classes allow for deinitialization, giving you the opportunity to clean up resources or notify other parts of your system about the object’s lifecycle changes.

  • Complex Object Relationships

    When your objects have intricate relationships or hierarchies that benefit from reference semantics. Classes can help manage and navigate these relationships without duplicating data.

To summarize, it’s smart to use classes if you need the features that only classes provide: inheritance, identity, Objective-C interoperability, and scenarios where copying a value doesn’t make sense.

Quick Tip: In many scenarios, you can “upgrade” a struct to a class by merely changing its signature from struct { to class {. Going from class to struct is harder. When you follow the struct-default approach, you keep the option to upgrade to a class later on.

Conclusion

Choosing between structs and classes in Swift is a pivotal decision that can influence the efficiency and clarity of your code. While both have their unique strengths, the key lies in understanding the specific needs of your application and making an informed choice. By striking the right balance, developers can harness the full potential of Swift and create robust, maintainable applications.


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts