Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2022 EssentialsSwift

WWDC22 · 29 min · Essentials / Swift

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.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 30 snippets

Tasks swift · at 1:18 ↗
Task.detached {
  let fish = await catchFish()
  let dinner = await cook(fish)
  await eat(dinner)
}
What is the pineapple? swift · at 2:31 ↗
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 {  }
}
Adding chickens swift · at 3:15 ↗
final class Chicken {
  let name: String
  var currentHunger: HungerLevel
  
  func feed() {  }
  func play() {  }
  func produce() -> Egg {  }
}
Sendable protocol swift · at 4:35 ↗
protocol Sendable { }
Use conformance to specify which types are Sendable swift · at 4:44 ↗
struct Pineapple: Sendable {  } //conforms to Sendable because its a value type
class Chicken: Sendable { } // cannot conform to Sendable because its an unsynchronized reference type.
Check Sendable across task boundaries swift · at 4:57 ↗
// will get an error because Chicken is not Sendable
let petAdoption = Task {
  let chickens = await hatchNewFlock()
  return chickens.randomElement()!
}
let pet = await petAdoption.value
The Sendable constraint is from the Task struct swift · at 5:26 ↗
struct Task<Success: Sendable, Failure: Error> {
  var value: Success {
    get async throws {  }
  }
}
Sendable checking for enums and structs swift · at 6:23 ↗
enum Ripeness: Sendable {
  case hard
  case perfect
  case mushy(daysPast: Int)
}

struct Pineapple: Sendable {
  var weight: Double
  var ripeness: Ripeness
}
Sendable checking for enums and structs with collections swift · at 6:52 ↗
//contains an array of Sendable types, therefore is Sendable
struct Crate: Sendable {
  var pineapples: [Pineapple]
}
Sendable checking for enums and structs with non-Sendable collections swift · at 7:17 ↗
//stored property 'flock' of 'Sendable'-conforming struct 'Coop' has non-sendable type '[Chicken]'
struct Coop: Sendable {
  var flock: [Chicken]
}
Sendable checking in classes swift · at 7:36 ↗
//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
}
Reference types that do their own internal synchronization swift · at 7:58 ↗
//@unchecked can be used, but be careful!
class ConcurrentCache<Key: Hashable & Sendable, Value: Sendable>: @unchecked Sendable {
  var lock: NSLock
  var storage: [Key: Value]
}
Sendable checking during task creation swift · at 8:21 ↗
let lily = Chicken(name: "Lily")
Task.detached {@Sendable in
	lily.feed()
}
Sendable function types swift · at 9:08 ↗
struct Task<Success: Sendable, Failure: Error> {
  static func detached(
    priority: TaskPriority? = nil,
    operation: @Sendable @escaping () async throws -> Success
  ) -> Task<Success, Failure>
}
Actors swift · at 10:28 ↗
actor Island {
  var flock: [Chicken]
  var food: [Pineapple]

  func advanceTime()
}
Only one boat can visit an island at a time swift · at 11:03 ↗
func nextRound(islands: [Island]) async {
  for island in islands {
    await island.advanceTime()
  }
}
Non-Sendable data cannot be shared between a task and actor swift · at 11:34 ↗
//Both examples cannot be shared
await myIsland.addToFlock(myChicken)
myChicken = await myIsland.adoptPet()
What code is actor-isolated? swift · at 12:43 ↗
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")
    }
  }
}
Nonisolated code swift · at 14:03 ↗
extension Island {
  nonisolated func meetTheFlock() async {
    let flockNames = await flock.map { $0.name }
    print("Meet our fabulous flock: \(flockNames)")
  }
}
Non-isolated synchronous code swift · at 14:48 ↗
func greet(_ friend: Chicken) { }

extension Island {
  func greetOne() {
    if let friend = flock.randomElement() { 
      greet(friend)
    }
  }
}
Non-isolated asynchronous code swift · at 15:15 ↗
func greet(_ friend: Chicken) { }

func greetAny(flock: [Chicken]) async {
  if let friend = flock.randomElement() { 
    greet(friend)
  }
}
Isolating functions to the main actor swift · at 17:01 ↗
@MainActor func updateView() {  }

Task { @MainActor in
	// …
  view.selectedChicken = lily
}

nonisolated func computeAndUpdate() async {
  computeNewValues()
  await updateView()
}
@MainActor types swift · at 17:38 ↗
@MainActor
class ChickenValley: Sendable {
  var flock: [Chicken]
  var food: [Pineapple]

  func advanceTime() {
    for chicken in flock {
      chicken.eat(from: &food)
    }
  }
}
Non-transactional code swift · at 19:58 ↗
func deposit(pineapples: [Pineapple], onto island: Island) async {
   var food = await island.food
   food += pineapples
   await island.food = food
}
Pirates! swift · at 20:56 ↗
await island.food.takeAll()
Modify `deposit` function to be synchronous swift · at 21:57 ↗
extension Island {
   func deposit(pineapples: [Pineapple]) {
      var food = self.food
      food += pineapples
      self.food = food
   }
}
AsyncStreams deliver elements in order swift · at 23:56 ↗
for await event in eventStream {
  await process(event)
}
Minimal strict concurrency checking swift · at 25:02 ↗
import FarmAnimals
struct Coop: Sendable {
  var flock: [Chicken]
}
Targeted strict concurrency checking swift · at 25:21 ↗
@preconcurrency import FarmAnimals

func visit(coop: Coop) async {
  guard let favorite = coop.flock.randomElement() else {
    return
  }

  Task {
    favorite.play()
  }
}
Complete strict concurrency checking swift · at 26:53 ↗
import FarmAnimals

func doWork(_ body: @Sendable @escaping () -> Void) {
  DispatchQueue.global().async {
    body()
  }
}

func visit(friend: Chicken) {
  doWork {
    friend.play()
  }
}

Resources