Class vs Struct vs Actor & MainActor

Lakshminaidu C
4 min readJul 20, 2023

--

In Swift, actor, struct, and class are three distinct constructs used for defining data structures and managing concurrency. Each has its own purpose and characteristics. Let's go through their differences:

Struct (struct):

  • A struct is a value type in Swift, which means it is copied when assigned to a new variable or passed as a function argument. It's suitable for representing lightweight data structures.
  • Structs don’t support inheritance. They are final by default, which means you cannot subclass them.
  • Structs are immutable by default. If you want to modify their properties, you need to mark the properties as var.
  • They are usually used for small data models, like coordinates, simple configurations, or other value-like entities.

Example of a struct:

struct Point { 
var x: Int
var y: Int
}
var p1 = Point(x: 1, y: 2)
var p2 = p1 // Copying the struct
p2.x = 3 print(p1.x) // Output: 1 (p1 remains unchanged)
print(p2.x) // Output: 3

Class (class):

  • A class is a reference type in Swift, which means when you assign a class instance to another variable or pass it as an argument, you are passing a reference to the same instance in memory.
  • Classes support inheritance, making them suitable for building complex object hierarchies.
  • Classes can be marked as final to prevent inheritance or open to allow subclassing (by default, they are open).
  • They support the concept of deinitializers (deinit) to clean up resources when the instance is deallocated.
  • Classes are generally used when you need to manage more complex objects with shared references.

Example of a class:

class Person { 
var name: String
init(name: String) {
self.name = name
}
}
var person1 = Person(name: "Alice")
var person2 = person1 // Both variables now reference the same instance
person2.name = "Bob"
print(person1.name) // Output: "Bob" (changes are reflected in both variables)
print(person2.name) // Output: "Bob"

Actor (actor):

  • An actor is a special type introduced in Swift to manage concurrency safely. It is used for encapsulating mutable state and ensuring that access to that state is serialized and protected from data races.
  • Actors are reference types, similar to classes, but they have built-in support for isolation and synchronization of their state, making them safe to use in concurrent scenarios.
  • To access the properties and methods of an actor, you need to use the await keyword, which ensures that the access is serialized and coordinated.
  • Actors are ideal for handling concurrent operations and shared resources without worrying about explicit locking.

Example of an actor:

actor BankAccount { 
private var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) -> Double {
let newBalance = balance - amount
if newBalance >= 0 {
balance = newBalance
return balance
} else {
return 0
}
}
}
let account = BankAccount()
Task {
await account.deposit(amount: 100) // balance 100
let withdrawnAmount = await account.withdraw(amount: 50) // balance 50
print(withdrawnAmount) // Output: 50
await account.deposit(amount: 25) // balance 75
let withdrawnAmount2 = await account.withdraw(amount: 50) // balance 25
print(withdrawnAmount2) // Output: 25
}

To summarize, you choose between struct, class, or actor based on the specific requirements of your application. Use struct for simple data modeling, class for managing complex object hierarchies, and actor for safe concurrent access to mutable state. Check below for more easy way.

We another Keyword MainActor

MainActor (@MainActor): == DispatchQueue.main.async()

  • @MainActor is an attribute to mark parts of your code that should be executed on the main (UI) thread. It is particularly useful in concurrent programming to ensure that certain operations that involve UI updates or other main-thread-only tasks are performed on the main thread. This helps avoid race conditions and synchronization issues.
  • @MainActor is a powerful tool for making your code more concurrent-friendly and ensuring that main-thread-only operations are performed safely.
  • Can be used on Class level, properties and methods. When you call a method or access a property marked with the @MainActor attribute from a different thread, Swift will automatically dispatch that work onto the main thread, ensuring that the code is executed safely
  • To access the properties and methods of an @MainActor, you need to use the await keyword, which ensures that the access is serialized and coordinated.
  • @MainActor is a global actor that uses the main queue for executing its work
class ViewModel {
@MainActor
func updateLabel() {
// Update UI elements here
self.titleLabel.text = "Updated on the main thread"
}

func someConcurrentFunction() async {
// ...
await updateLabel() // This will be dispatched to the main thread
// ...
}
}

Value Types and Reference Types

As discussed, in Swift there are two types of instances:

First Value Types, where each instance keeps a unique copy of its data. Int, String, Array, Dictionary, Float, Double, struct, enum, & Tuple all are value types.

The second, Reference Types, where instances share a single copy of the data, and the type is usually defined as a Class, NSObject & UIKIT elements.

Use a value type when:

  • Comparing instance data with == makes sense
  • You want copies to have independent state
  • The data will be used in code across multiple threads

Use a reference type (e.g. use a class) when:

  • Comparing instance identity with === makes sense
  • You want to create shared, mutable state

Follow me Lakshminaidu C for more content.

--

--