Understanding Swift’s Struct, Class, and the New Actor

Rahul verma
Simpplr Technology
3 min readFeb 21, 2024

--

Swift empowers developers with diverse tools for managing data and concurrency. Among these champions stand structs, classes, and the newly introduced actor. Let’s explore their differences and delve into the actor’s unique ability to handle shared mutable state safely in a multi-threaded environment.

Structs vs Classes/Actors: Value vs Reference

Value Type:

  1. Struct, Enum, String, Int, etc.
  2. Stored in Stack
  3. Faster than reference types
  4. Thread safe
  5. When you assign or pass value type a new copy of data is created.

Reference type:

  1. Class, Function, Actors
  2. Stored in Heap
  3. Slower than value types. However, synchronization is guaranteed (if the value of the reference type is changed at a specific reference pointer, synchronization is guaranteed at other reference pointers as well)
  4. Not Thread safe (Except Actor)
  5. When you assign or pass reference type a new reference to the original instance will be created (pointer).

Introducing Actors: Concurrency Simplified

Actors: Enter Swift 5.5, bringing a game-changer for concurrency. Actors encapsulate state and behavior, guaranteeing only one task accesses the mutable state at a time. This built-in thread safety eliminates data races and the need for complex locking mechanisms.

Enough Talk, Let’s Code: Swift’s Structs, Classes, and Actors Unveiled!

Struct:

// Struct (Pass by Value)
struct SafeCounter {
var count = 0

mutating func increment() {
count += 1
}
}

var structCounter = SafeCounter()

// Showcasing thread safety
// Concurrent tasks incrementing the count using structs
for _ in 0..<10 {
DispatchQueue.global().async {
structCounter.increment()
print(structCounter.count) // Output: Incremented count (1, 2, 3, ..., 10)
}
}

// Pass by value behavior
var copyOfCounter = structCounter // Creating a copy
copyOfCounter.increment()
print("Original structCounter count: \(structCounter.count)") // Output: Original structCounter count: 0
print("Copy of structCounter count: \(copyOfCounter.count)") // Output: Copy of structCounter count: 1

Class:

// Class (Pass by Reference)
class UnsafeCounter {
private var count = 0

func increment() {
count += 1
}

func getCount() -> Int {
return count
}
}

let unsafeCounter = UnsafeCounter()

// Showcasing classes aren't thread safe
// Concurrent tasks incrementing the count using classes (may cause issues)
DispatchQueue.concurrentPerform(iterations: 100) { _ in
unsafeCounter.increment()
print(unsafeCounter.getCount()) // Output may vary, likely inconsistent or incorrect
}

// Pass by reference behavior
let unsafeCounterInstance = UnsafeCounter()
let referenceToCounter = unsafeCounterInstance // Creating a reference
referenceToCounter.increment()
print("Original unsafeCounter count: \(unsafeCounterInstance.getCount())") // Output: Original unsafeCounter count: 1
print("Reference to unsafeCounter count: \(referenceToCounter.getCount())") // Output: Reference to unsafeCounter count: 1

Actor:

// Actor (Pass by Reference)
actor Counter {
private var count = 0

func increment() {
count += 1
}

func getCount() -> Int {
return count
}
}

let actorCounter = Counter()

// Showcasing actors are thread safe
// Concurrent tasks incrementing the count using actors
for _ in 0..<10 {
Task {
await actorCounter.increment()
print(await actorCounter.getCount()) // Output: Incremented count (1, 2, 3, ..., 10)
}
}

// Pass by reference behavior is same as that of classes

Why Choose Actors?

  1. Fearless Concurrency: With actors, race conditions and deadlocks become distant memories. Thread safety is baked in, freeing you to focus on application logic.
  2. Encapsulation Masters: Actors encapsulate both state and behavior, creating clear boundaries and simplified reasoning about concurrency in complex systems.
  3. Async Powerhouse: Actors excel at asynchronous communication, enabling efficient parallelism and responsive applications. Build scalable and performant systems with confidence.
  4. Error Handling Grace: Actors handle errors within their context, simplifying error management and improving application reliability.

Conclusion: The Right Tool for the Job

Each construct has its strengths. Structs reign for immutable data, classes tackle complex objects, and actors conquer shared mutable state with thread safety. Understanding their nuances empowers you to build robust and scalable applications in the ever-evolving world of software development.

Remember: While actors are powerful, choose the right tool for the job. For purely sequential operations, structs or classes might suffice. However, when concurrency and shared state come into play, actors step up as the champions of safety and simplicity.

Explore further:

I hope this medium article meets your expectations!
Happy coding!

--

--

Rahul verma
Simpplr Technology

I'm Rahul Verma, an iOS developer with 3.5 yrs of experience. Follow me for insights on building better mobile apps and industry trends. #iOSDev #MobileApps