Greetings, traveler!
Today, we will discuss thread safety once again. Earlier, we discussed this with examples related to singletons and global actors, as well as the creation of custom structures and classes for atomic operations. This article will discuss a new way to handle atomic operations with a new Apple framework called Synchronization
, which is available with iOS 18. If you want to support older versions of iOS, you can implement something like this.
Note
By the way, you can read more about the Singleton design pattern, find out why so many developers say it is an antipattern, and learn how to fix all these issues.
Mutex
Today, we’ll examine the Mutex
structure and how it can prevent crashes and undefined behavior caused by simultaneous access to shared mutable data.
First, let’s take a look at some unsafe code that could lead to app crashes:
final class Singleton {
static let shared: Singleton = .init()
var value: String = ""
private init() {}
}
This singleton example can be accessed from anywhere in the app. It’s vulnerable because multiple threads could access or modify the cache simultaneously, potentially causing race conditions or crashes.
Since we have already discussed different approaches to prevent data races, let’s jump straight to the Mutex
.
We can mark our singleton as Sendable
and import the Synchronization
framework. After that, you can use Mutex
:
final class Singleton: Sendable {
static let shared: Singleton = .init()
var value: String {
get {
_value.withLock { value in
value
}
}
set {
_value.withLock { value in
value = newValue
}
}
}
private let _value: Mutex<String> = .init("")
private init() {}
}
We can protect the shared mutable state by using Mutex. The withLock
method ensures that only one thread can access the cache simultaneously, preventing data races.
If you enjoyed this article, please feel free to follow me on my social media: