Actors and Data Races in Swift

Concurrency with async/await — Part 5

Tim Dolenko
Axel Springer Tech
3 min readAug 15, 2022

--

👆watch it if you prefer video

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:

Data Race (example)

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 🔥

--

--