-
Eliminate data races using Swift Concurrency
Join us as we explore one of the core concepts in Swift concurrency: isolation of tasks and actors. We'll take you through Swift's approach to eliminating data races and its effect on app architecture. We'll also discuss the importance of atomicity in your code, share the nuances of Sendable checking to maintain isolation, and revisit assumptions about ordering work in a concurrent system.
Ressources
Vidéos connexes
WWDC22
- Meet distributed actors in Swift
- Visualize and optimize Swift concurrency
- What's new in Swift
- What's new in UIKit
WWDC21
-
Rechercher dans cette vidéo…
-
-
1:18 - Tasks
Task.detached { let fish = await catchFish() let dinner = await cook(fish) await eat(dinner) } -
2:31 - What is the pineapple?
enum Ripeness { case hard case perfect case mushy(daysPast: Int) } struct Pineapple { var weight: Double var ripeness: Ripeness mutating func ripen() async { … } mutating func slice() -> Int { … } } -
3:15 - Adding chickens
final class Chicken { let name: String var currentHunger: HungerLevel func feed() { … } func play() { … } func produce() -> Egg { … } } -
4:35 - Sendable protocol
protocol Sendable { } -
4:44 - Use conformance to specify which types are Sendable
struct Pineapple: Sendable { … } //conforms to Sendable because its a value type class Chicken: Sendable { } // cannot conform to Sendable because its an unsynchronized reference type. -
4:57 - Check Sendable across task boundaries
// will get an error because Chicken is not Sendable let petAdoption = Task { let chickens = await hatchNewFlock() return chickens.randomElement()! } let pet = await petAdoption.value -
5:26 - The Sendable constraint is from the Task struct
struct Task<Success: Sendable, Failure: Error> { var value: Success { get async throws { … } } } -
6:23 - Sendable checking for enums and structs
enum Ripeness: Sendable { case hard case perfect case mushy(daysPast: Int) } struct Pineapple: Sendable { var weight: Double var ripeness: Ripeness } -
6:52 - Sendable checking for enums and structs with collections
//contains an array of Sendable types, therefore is Sendable struct Crate: Sendable { var pineapples: [Pineapple] } -
7:17 - Sendable checking for enums and structs with non-Sendable collections
//stored property 'flock' of 'Sendable'-conforming struct 'Coop' has non-sendable type '[Chicken]' struct Coop: Sendable { var flock: [Chicken] } -
7:36 - Sendable checking in classes
//Can be Sendable if a final class has immutable storage final class Chicken: Sendable { let name: String var currentHunger: HungerLevel //'currentHunger' is mutable, therefore Chicken cannot be Sendable } -
7:58 - Reference types that do their own internal synchronization
//@unchecked can be used, but be careful! class ConcurrentCache<Key: Hashable & Sendable, Value: Sendable>: @unchecked Sendable { var lock: NSLock var storage: [Key: Value] } -
8:21 - Sendable checking during task creation
let lily = Chicken(name: "Lily") Task.detached {@Sendable in lily.feed() } -
9:08 - Sendable function types
struct Task<Success: Sendable, Failure: Error> { static func detached( priority: TaskPriority? = nil, operation: @Sendable @escaping () async throws -> Success ) -> Task<Success, Failure> } -
10:28 - Actors
actor Island { var flock: [Chicken] var food: [Pineapple] func advanceTime() } -
11:03 - Only one boat can visit an island at a time
func nextRound(islands: [Island]) async { for island in islands { await island.advanceTime() } } -
11:34 - Non-Sendable data cannot be shared between a task and actor
//Both examples cannot be shared await myIsland.addToFlock(myChicken) myChicken = await myIsland.adoptPet() -
12:43 - What code is actor-isolated?
actor Island { var flock: [Chicken] var food: [Pineapple] func advanceTime() { let totalSlices = food.indices.reduce(0) { (total, nextIndex) in total + food[nextIndex].slice() } Task { flock.map(Chicken.produce) } Task.detached { let ripePineapples = await food.filter { $0.ripeness == .perfect } print("There are \(ripePineapples.count) ripe pineapples on the island") } } } -
14:03 - Nonisolated code
extension Island { nonisolated func meetTheFlock() async { let flockNames = await flock.map { $0.name } print("Meet our fabulous flock: \(flockNames)") } } -
14:48 - Non-isolated synchronous code
func greet(_ friend: Chicken) { } extension Island { func greetOne() { if let friend = flock.randomElement() { greet(friend) } } } -
15:15 - Non-isolated asynchronous code
func greet(_ friend: Chicken) { } func greetAny(flock: [Chicken]) async { if let friend = flock.randomElement() { greet(friend) } } -
17:01 - Isolating functions to the main actor
@MainActor func updateView() { … } Task { @MainActor in // … view.selectedChicken = lily } nonisolated func computeAndUpdate() async { computeNewValues() await updateView() } -
17:38 - @MainActor types
@MainActor class ChickenValley: Sendable { var flock: [Chicken] var food: [Pineapple] func advanceTime() { for chicken in flock { chicken.eat(from: &food) } } } -
19:58 - Non-transactional code
func deposit(pineapples: [Pineapple], onto island: Island) async { var food = await island.food food += pineapples await island.food = food } -
20:56 - Pirates!
await island.food.takeAll() -
21:57 - Modify `deposit` function to be synchronous
extension Island { func deposit(pineapples: [Pineapple]) { var food = self.food food += pineapples self.food = food } } -
23:56 - AsyncStreams deliver elements in order
for await event in eventStream { await process(event) } -
25:02 - Minimal strict concurrency checking
import FarmAnimals struct Coop: Sendable { var flock: [Chicken] } -
25:21 - Targeted strict concurrency checking
@preconcurrency import FarmAnimals func visit(coop: Coop) async { guard let favorite = coop.flock.randomElement() else { return } Task { favorite.play() } } -
26:53 - Complete strict concurrency checking
import FarmAnimals func doWork(_ body: @Sendable @escaping () -> Void) { DispatchQueue.global().async { body() } } func visit(friend: Chicken) { doWork { friend.play() } }
-