Article by Pushkar Deshmukh
Swift Actors in iOS Architecture: The Future of Thread Safety
Thread safety in iOS apps has traditionally relied on DispatchQueue, locks, and serial queues. Swift introduced Actors to solve this problem at the language level. This article walks through the evolution of thread safety in Swift using simple examples and shows how Actors simplify concurrency in modern iOS architectures.
Pushkar Deshmukh
Senior iOS Engineer

The Problem: Shared Mutable State
Modern iOS apps execute many tasks concurrently:
network requests
background processing
caching
database operations
When multiple threads modify the same data, we risk data races.
Example:
var count = 0
DispatchQueue.global().async {
count += 1
}
DispatchQueue.global().async {
count += 1
}
Both tasks might update count simultaneously, producing unpredictable results.
To solve this, developers historically relied on several techniques.
1️⃣ Thread Safety Using DispatchQueue
A common solution is protecting state with a queue.
class CounterStore {
private var count = 0
private let queue = DispatchQueue(label: "counter.queue")
func increment() {
queue.sync {
count += 1
}
}
func getCount() -> Int {
queue.sync {
count
}
}
}
Usage:
let store = CounterStore()
DispatchQueue.global().async {
store.increment()
}
DispatchQueue.global().async {
store.increment()
}
The queue ensures only one thread modifies the state at a time.
⚠️ Problem:
Thread safety relies on always accessing the state through the queue.
The compiler cannot enforce this.
2️⃣ Thread Safety Using NSLock
Another traditional approach is explicit locking.
class CounterStore {
private var count = 0
private let lock = NSLock()
func increment() {
lock.lock()
count += 1
lock.unlock()
}
func getCount() -> Int {
lock.lock()
let value = count
lock.unlock()
return value
}
}
Locks guarantee that only one thread accesses the state at a time.
⚠️ Downsides:
easy to forget
unlock()risk of deadlocks
harder to maintain in complex systems
3️⃣ Serial Queues
A serial queue executes tasks one at a time in order.
class CounterStore {
private var count = 0
private let queue = DispatchQueue(label: "counter.serial")
func increment() {
queue.async {
self.count += 1
}
}
func getCount(completion: @escaping (Int) -> Void) {
queue.async {
completion(self.count)
}
}
}
Serial queues ensure that mutations happen sequentially, preventing race conditions.
⚠️ Downsides:
introduces callback-based APIs
safety still relies on discipline
direct state access can still break safety
4️⃣ Concurrent Queues
Concurrent queues allow multiple tasks to run simultaneously.
let queue = DispatchQueue.global()
queue.async {
print("Task 1")
}
queue.async {
print("Task 2")
}
Execution order is not guaranteed, and tasks may run in parallel.
If shared state is modified:
var count = 0
DispatchQueue.global().async {
count += 1
}
DispatchQueue.global().async {
count += 1
}
This creates a race condition.
Concurrent Queue + Barrier Pattern
To improve performance in read-heavy systems, developers sometimes use barriers.
class CounterStore {
private var count = 0
private let queue = DispatchQueue(
label: "counter.concurrent",
attributes: .concurrent
)
func increment() {
queue.async(flags: .barrier) {
self.count += 1
}
}
func getCount(completion: @escaping (Int) -> Void) {
queue.async {
completion(self.count)
}
}
}
How it works:
multiple reads run concurrently
writes are exclusive due to the barrier
⚠️ Still, safety depends on developers always using the queue correctly.
The Core Problem
All these techniques share one issue:
Thread safety depends on developer discipline.
The compiler cannot enforce correct usage.
This is exactly what Swift Actors solve.
5️⃣ Swift Actors: Thread Safety by Design
Swift introduced Actors to provide built-in isolation for mutable state.
actor CounterStore {
private var count = 0
func increment() {
count += 1
}
func getCount() -> Int {
count
}
}
Usage:
let store = CounterStore()
Task {
await store.increment()
}
Task {
let value = await store.getCount()
print(value)
}
Actors guarantee:
only one task accesses state at a time
no locks required
no queues required
Thread safety becomes a language-level guarantee.
Real iOS Example: Thread-Safe Cache
Actors work extremely well for shared services like caching.
actor ImageCache {
private var storage: [String: Data] = [:]
func save(_ data: Data, for key: String) {
storage[key] = data
}
func get(for key: String) -> Data? {
storage[key]
}
}
Usage:
let cache = ImageCache()
Task {
await cache.save(imageData, for: "profile")
}
Task {
let data = await cache.get(for: "profile")
}
Even if multiple tasks access the cache concurrently, the actor guarantees safe access.
Actors in Real iOS Architecture
Actors become extremely powerful in the data layer.
Typical use cases:
repositories
caching systems
session management
shared services
Example repository:
actor UserRepository {
private var cachedUser: User?
func fetchUser() async throws -> User {
if let user = cachedUser {
return user
}
let user = try await APIClient.fetchUser()
cachedUser = user
return user
}
}
This repository becomes naturally thread-safe.
No locks or queues are needed.
UI Layer: Using MainActor
UI updates must happen on the main thread.
Swift provides a special actor for this:
@MainActor
Example ViewModel:
@MainActor
class ProfileViewModel {
private let repository = UserRepository()
func loadUser() async throws -> User {
try await repository.fetchUser()
}
}
Architecture becomes clear:
Actors → shared background state
MainActor → UI state
Evolution of Thread Safety in Swift
Approach | Thread Safety | Complexity | Compiler Enforcement |
|---|---|---|---|
NSLock | Medium | High | ❌ |
DispatchQueue | Medium | Medium | ❌ |
Serial Queue | Medium | Medium | ❌ |
Concurrent + Barrier | Medium | High | ❌ |
Swift Actors | High | Low | ✅ |
Actors move concurrency safety from developer discipline → language guarantees.
Final Thoughts
Concurrency bugs are among the hardest problems to debug in production systems.
Swift Actors simplify this dramatically.
Instead of protecting shared state with locks and queues, we now isolate state by design.
For modern Swift concurrency architectures, Actors are quickly becoming a core building block for safe and scalable iOS systems.




Comments
Loading comments…