What’s Happening in Swift 5.10
One step more further to safety by eliminating programing mistakes at compile time. Swift have already catered many undefined behavior which we can witness in the C-based languages.
Data races
A data race occurs when the same memory is accessed from multiple threads without synchronization, and at least one is a write access. This leads to unpredictable behavior as the order of execution determines the outcome. For instance, if two different threads access the same String property where one thread writes to it, causing at least one write access, this situation exemplifies a data race. The behavior becomes unpredictable depending on whether the print statement or the write operation is executed first. In short a data race signifies a scenario where concurrent threads access shared memory without proper synchronization, leading to potential issues like unexpected behavior and crashes in the code.
import Foundation
var sharedValue = 0
let queue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
for _ in 0..<10 {
queue.async {
for _ in 0..<1000 {
sharedValue += 1
}
}
}
// To ensure all tasks are completed before printing the final value
queue.sync {}
print("Final value of sharedValue: \(sharedValue)")
Data Isolation
Data isolation refers to the mechanism that prevents data races by ensuring that memory is accessed in a controlled manner, allowing the compiler to reason about how data is accessed and when. It involves protecting shared data from concurrent access by multiple threads without proper synchronization. Data isolation is achieved through features like actors, which provide a model for isolating state in concurrent programs to eliminate data races.
By enforcing data isolation, Swift can avoid data races by ensuring that only one thread can access a specific piece of memory at a given time, thus preventing conflicting read and write operations that lead to unpredictable behavior. This approach makes concurrent programming safer and more reliable by eliminating the risk of data races, which can cause crashes and errors due to corrupted internal state when multiple threads access the same memory concurrently
actor SharedCounter {
private var count = 0
func increment() { count += 1 }
func getCount() -> Int { return count }
}
let counter = SharedCounter()
let queue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
for _ in 0..<10 {
queue.async {
counter.increment()
}
}
// To ensure all tasks are completed before printing the final count
queue.sync {}
print("Final count: \(counter.getCount())")
What’s there in swift 5.10
Here comes the Full Data Isolation, in Swift 5.10, the language enhances data-race safety across all aspects and addresses issues in Sendable and actor isolation checks to bolster complete concurrency verification.
We can enable it by marking the compiler flag “-strict-concurrency=complete”. This identifies potential data races during compilation, except when explicitly opting out with nonisolated(unsafe) or @unchecked Sendable annotations for specific scenarios.
For instance, in Swift 5.9, a code snippet like the following could lead to data races due to incorrect isolation handling:
@MainActor
class MyModel {
static let shared = MyModel()
private init() {
MainActor.assertIsolated()
}
}
func useShared() async {
let model = MyModel.shared
}
await useShared()
Swift 5.10 under “-strict-concurrency=complete” warns about synchronous access to @MainActor-isolated variables, suggesting asynchronous access for data-race prevention.
Unsafe Opt-Outs
Unsafe opt-outs like @unchecked Sendable conformances are crucial for indicating code safety from data races when compiler verification is challenging.
In Swift 5.10, a new keyword “nonisolated(unsafe)” is introduced to opt out of actor isolation checks for stored properties and variables, offering more granular control over concurrency safety.
For example, applying nonisolated(unsafe) to global/static variables can address concurrency warnings without compromising safety:
import Dispatch
struct MyData {
static let cacheQueue = DispatchQueue(…)
nonisolated(unsafe) static var globalCache: [MyData] = []
}
nonisolated(unsafe) eliminates the need for @unchecked Sendable wrappers in scenarios where specific instances require opt-out from Sendable constraints across isolation boundaries.
A step forward to swift 6.0
In swift 6.0 we can expect full data isolation by default, which would be enthralling change in swift development.
Thank you for reading 🧑🏻💻
Be sure to clap 👏🏼 and follow 🚶🏻♂️
Questions❓Feedback 📫 — please drop you comments 💭
If you like this article, feel free to share it with your friends 📨
Follow me: Linkedin | X(Twitter) | Github 🤝🏼