Actors and Data Races in Swift
Concurrency with async/await — Part 5
How to create a data race in Swift?
I created a sample screen, to demonstrate how we might create a data race in Swift.
We iterate in a loop 1000 times and each time call a function from global and main queues. The function just increments an integer.
Have a look at BankAccount and DonationsViewModel:
Here’s an example of data race in Swift:
We call deposit 2 times and expect the balance to be 200. But it’s actually 100! It’s called a data race. And it’s a bug.
Before running the app, edit the current scheme and enable “Thread Sanitizer”
Run the app and tap the first button on the screen!
We have a couple of problems. We expect to have $200,000 (2 function calls * 1000 iterations * 100 each increment), but we get only a fraction of it displayed on the screen, and on the alert, we see that we lack $300. Not good.
We also see that we have a data race warning triggered by the Thread Sanitizer:
To fix it, turn the BankAccount into actor
replacing class
with actor
:
actor BankAccount { ... }
An actor is a special type that guarantees that access to its members will be granted to only one thread at a time. No more data races! This time, Thread B will just wait until Thread A finishes calling the deposit()
function. An actor is a reference type.
Let’s fix compile issues.
Now every member of BankAccount
is async
and has to be awaited. With a deposit()
it makes sense, but with other static things like bankDetails()
it makes no sense.
nonisolated func bankDetails() -> String { iban }
nonisolated
will fix it. It can make a part of actor
accessible by awaiting it.
Let’s fix receiveDeposits()
by making it async
and waiting for the bankAccount.balance
, and self.depositAndDisplay()
.
public func depositAndDisplay() async {
let result = await bankAccount.deposit()
...public func checkResults() async {
let actualBalance = await bankAccount.balance
...
The same applies to depositAndDisplay()
and checkResults()
.
Make DonationsViewModel
@MainActor
and run the app!
That concludes our ASYNC/AWAIT series!
Congrats on sticking till the end 🔥