📖 Scenario: You are building a simple bank account system where multiple parts of your app can deposit and withdraw money. You want to keep the balance safe from mistakes when many parts try to change it at the same time.
🎯 Goal: Learn how to use actor and lock to protect shared data and decide which one to use.
📋 What You'll Learn
Create a class BankAccountWithLock that uses a lock to protect the balance
Create an actor called BankAccountActor that protects the balance automatically
Write code to deposit and withdraw money safely in both versions
Print the final balances from both versions to compare
💡 Why This Matters
🌍 Real World
Banking apps, games, or any app where multiple parts change shared data at the same time.
💼 Career
Understanding concurrency safety is key for writing reliable Swift apps, especially with Swift's new actor model.
Progress0 / 4 steps
1
Create BankAccountWithLock class with balance and lock
Create a class called BankAccountWithLock with a balance property set to 0. Add a NSLock property called lock to protect the balance.
Swift
Hint
Use NSLock() to create the lock. Keep balance private.
2
Add deposit and withdraw methods with lock
Add two methods to BankAccountWithLock: deposit(amount: Int) and withdraw(amount: Int). Use lock.lock() before changing balance and lock.unlock() after.
Swift
Hint
Remember to lock before changing balance and unlock after.
3
Create BankAccountActor with async deposit and withdraw
Create an actor called BankAccountActor with a balance property set to 0. Add async methods deposit(amount: Int) and withdraw(amount: Int) that update balance safely.
Swift
Hint
Actors protect data automatically, so no locks needed inside.
4
Use both accounts and print final balances
Create an instance of BankAccountWithLock called lockedAccount and an instance of BankAccountActor called actorAccount. Deposit 100 and withdraw 40 on both. Print the final balances for both accounts.
Swift
Hint
Use Task to call async methods on the actor. Add getBalance() methods to read balances.
Practice
(1/5)
1. Which of the following best describes the main advantage of using actors in Swift concurrency?
easy
A. They automatically protect data from concurrent access without manual locking.
B. They provide manual control over thread synchronization using locks.
C. They are used only for UI updates in Swift applications.
D. They require explicit lock management to avoid race conditions.
Solution
Step 1: Understand what actors do in Swift concurrency
Actors automatically serialize access to their mutable state, preventing data races without manual locks.
Step 2: Compare actors with locks
Locks require manual management to protect data, while actors handle this automatically.
Final Answer:
They automatically protect data from concurrent access without manual locking. -> Option A
2. Which of the following is the correct way to declare an actor in Swift?
easy
A. class MyActor {}
B. actor MyActor {}
C. struct MyActor {}
D. lock MyActor {}
Solution
Step 1: Recall Swift syntax for actors
Actors are declared using the keyword actor followed by the name and braces.
Step 2: Check each option
actor MyActor {} uses actor MyActor {}, which is the correct syntax. Others use class, struct, or invalid keyword.
Final Answer:
actor MyActor {} -> Option B
Quick Check:
Actor declaration uses 'actor' keyword [OK]
Hint: Actors start with 'actor' keyword, not class or struct. [OK]
Common Mistakes:
Using 'class' or 'struct' instead of 'actor'.
Trying to use 'lock' keyword which doesn't exist.
Forgetting braces after actor name.
3. Consider this Swift code snippet:
actor Counter {
var value = 0
func increment() {
value += 1
}
}
let counter = Counter()
Task {
await counter.increment()
print(await counter.value)
}
What will be printed when this code runs?
medium
A. Compilation error due to missing locks
B. 0
C. 1
D. Runtime crash due to data race
Solution
Step 1: Understand actor's behavior with state
The actor serializes access to its state, so increment() safely updates value.
Step 2: Analyze the code execution
The increment() method increases value from 0 to 1, then print outputs the updated value.
Final Answer:
1 -> Option C
Quick Check:
Actor state updated safely = 1 [OK]
Hint: Actors serialize access, so state updates are safe and visible. [OK]
Common Mistakes:
Expecting 0 because of async call.
Thinking a lock is needed causing error.
Assuming data race causes crash.
4. This Swift code uses a lock to protect shared data:
class Counter {
private var value = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
func getValue() -> Int {
lock.lock()
let val = value
lock.unlock()
return val
}
}
What is a potential problem with this code?
medium
A. It can cause a deadlock if increment() is called recursively.
B. It will not compile because NSLock is not imported.
C. It does not protect the value variable from concurrent access.
D. It uses actors incorrectly instead of locks.
Solution
Step 1: Analyze lock usage in increment()
The method locks before incrementing and unlocks after, but if increment() calls itself, it will try to lock again without unlocking first.
Step 2: Understand deadlock risk
NSLock is not reentrant, so recursive locking causes deadlock, freezing the program.
Final Answer:
It can cause a deadlock if increment() is called recursively. -> Option A
Quick Check:
NSLock + recursion = deadlock risk [OK]
Hint: NSLock is not reentrant; recursive locks cause deadlock. [OK]
Common Mistakes:
Assuming NSLock is reentrant.
Thinking code won't compile without import (usually imported).
Confusing actors with locks in this context.
5. You have a Swift app that needs to update a shared cache from multiple concurrent tasks. You want safety and simplicity without manual lock management. Which approach should you choose?
hard
A. Use a global variable without any synchronization.
B. Use NSLock to manually lock and unlock around cache updates.
C. Use DispatchQueue.sync to serialize access without actors.
D. Use an actor to manage the cache state and provide async methods.
Solution
Step 1: Identify requirements for safety and simplicity
Actors provide automatic data protection and simplify concurrency without manual locks.
Step 2: Compare options
NSLock requires manual lock management, global variable is unsafe, DispatchQueue.sync can block threads and is more complex.
Final Answer:
Use an actor to manage the cache state and provide async methods. -> Option D
Quick Check:
Actors = safe + simple concurrency [OK]
Hint: Actors simplify concurrency by managing safety automatically. [OK]
Common Mistakes:
Choosing manual locks for simplicity.
Ignoring thread safety with global variables.
Using DispatchQueue.sync causing potential deadlocks.
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.