View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • App Intents
    • Apple Intelligence
    • Games
    • Machine Learning & AI
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Vídeos

Abrir menu Fechar menu
  • Coleções
  • Todos os vídeos
  • Sobre

Mais vídeos

  • Sobre
  • Código
  • Migrate your app to Swift 6

    Experience Swift 6 migration in action as we update an existing sample app. Learn how to migrate incrementally, module by module, and how the compiler helps you identify code that's at risk of data races. Discover different techniques for ensuring clear isolation boundaries and eliminating concurrent access to shared mutable state.

    Capítulos

    • 0:00 - Introduction
    • 0:33 - The Coffee Tracker app
    • 0:45 - Review the refactor from WWDC21
    • 3:20 - Swift 6 and data-race safety
    • 4:40 - Swift 6 migration in practice
    • 7:26 - The strategy
    • 8:53 - Adopting concurrency features
    • 11:05 - Enabling complete checking in the watch extension
    • 13:05 - Shared mutable state in global variables
    • 17:04 - Shared mutable state in global instances and functions
    • 19:29 - Delegate callbacks and concurrency
    • 23:40 - Guaranteeing data-race safety with code you don’t maintain
    • 25:51 - Enabling the Swift 6 language mode in the watch extension
    • 26:35 - Moving on to CoffeeKit
    • 27:24 - Enabling complete checking in CoffeeKit
    • 27:47 - Common patterns and an incremental strategy
    • 29:55 - Global variables in CoffeeKit
    • 31:05 - Sending an array between actors
    • 33:53 - What if you can’t mark something as Sendable?
    • 35:23 - Enabling the Swift 6 language mode in CoffeeKit
    • 35:59 - Adding a new feature with guaranteed data-race safety
    • 40:43 - Wrap up and the Swift 6 migration guide

    Recursos

    • Swift Migration Guide
    • Updating an app to use strict concurrency
    • Forum: Programming Languages
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC21

    • Swift concurrency: Update a sample app
  • Buscar neste vídeo...
    • 9:08 - Recaffeinater and CaffeineThresholdDelegate

      //Define Recaffeinator class
      class Recaffeinater: ObservableObject {
          @Published var recaffeinate: Bool = false
          var minimumCaffeine: Double = 0.0
      }
      
      //Add protocol to notify if caffeine level is dangerously low
      extension Recaffeinater: CaffeineThresholdDelegate {
          public func caffeineLevel(at level: Double) {
              if level < minimumCaffeine {
                  // TODO: alert user to drink more coffee!
              }
          }
      }
    • 9:26 - Add @MainActor to isolate the Recaffeinator

      //Isolate the Recaffeinater class to the main actor
      @MainActor
      class Recaffeinater: ObservableObject {
          @Published var recaffeinate: Bool = false
          var minimumCaffeine: Double = 0.0
      }
    • 9:38 - Warning in the protocol implementation

      //warning: Main actor-isolated instance method 'caffeineLevel(at:)' cannot be used to satisfy nonisolated protocol requirement
      public func caffeineLevel(at level: Double) {
            if level < minimumCaffeine {
                // TODO: alert user to drink more coffee!
            }
      }
    • 9:59 - Understanding why the warning is there

      //This class is guaranteed on the main actor...
      @MainActor
      class Recaffeinater: ObservableObject {
          @Published var recaffeinate: Bool = false
          var minimumCaffeine: Double = 0.0
      }
      
      //...but this protocol is not
      extension Recaffeinater: CaffeineThresholdDelegate {
          public func caffeineLevel(at level: Double) {
              if level < minimumCaffeine {
                  // TODO: alert user to drink more coffee!
              }
          }
      }
    • 12:59 - A warning on the logger variable

      //var 'logger' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in the Swift 6 language mode
      var logger = Logger(
          subsystem:
              "com.example.apple-samplecode.Coffee-Tracker.watchkitapp.watchkitextension.ContentView",
          category: "Root View")
    • 13:38 - Option 1: Convert 'logger' to a 'let' constant

      //Option 1: Convert 'logger' to a 'let' constant to make 'Sendable' shared state immutable
      let logger = Logger(
          subsystem:
              "com.example.apple-samplecode.Coffee-Tracker.watchkitapp.watchkitextension.ContentView",
          category: "Root View")
    • 14:20 - Option 2: Isolate 'logger' it to the main actor

      //Option 2: Annotate 'logger' with '@MainActor' if property should only be accessed from the main actor
      @MainActor var logger = Logger(
          subsystem:
              "com.example.apple-samplecode.Coffee-Tracker.watchkitapp.watchkitextension.ContentView",
          category: "Root View")
    • 14:58 - Option 3: Mark it nonisolated(unsafe)

      //Option 3: Disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
      nonisolated(unsafe) var logger = Logger(
          subsystem:
              "com.example.apple-samplecode.Coffee-Tracker.watchkitapp.watchkitextension.ContentView",
          category: "Root View")
    • 15:43 - The right answer

      //Option 1: Convert 'logger' to a 'let' constant to make 'Sendable' shared state immutable
      let logger = Logger(
          subsystem:
              "com.example.apple-samplecode.Coffee-Tracker.watchkitapp.watchkitextension.ContentView",
          category: "Root View")
    • 17:03 - scheduleBackgroundRefreshTasks() has two warnings

      func scheduleBackgroundRefreshTasks() {
      
          scheduleLogger.debug("Scheduling a background task.")
      
          // Get the shared extension object.
          let watchExtension = WKApplication.shared() //warning: Call to main actor-isolated class method 'shared()' in a synchronous nonisolated context
      
          // If there is a complication on the watch face, the app should get at least four
          // updates an hour. So calculate a target date 15 minutes in the future.
          let targetDate = Date().addingTimeInterval(15.0 * 60.0)
      
          // Schedule the background refresh task.
          watchExtension.scheduleBackgroundRefresh(withPreferredDate: targetDate, userInfo: nil) { //warning: Call to main actor-isolated instance method 'scheduleBackgroundRefresh(withPreferredDate:userInfo:scheduledCompletion:)' in a synchronous nonisolated context
              error in
      
              // Check for errors.
              if let error {
                  scheduleLogger.error(
                      "An error occurred while scheduling a background refresh task: \(error.localizedDescription)"
                  )
                  return
              }
      
              scheduleLogger.debug("Task scheduled!")
          }
      }
    • 17:57 - Annotate function with @MainActor

      @MainActor func scheduleBackgroundRefreshTasks() {
      
          scheduleLogger.debug("Scheduling a background task.")
      
          // Get the shared extension object.
          let watchExtension = WKApplication.shared()
      
          // If there is a complication on the watch face, the app should get at least four
          // updates an hour. So calculate a target date 15 minutes in the future.
          let targetDate = Date().addingTimeInterval(15.0 * 60.0)
      
          // Schedule the background refresh task.
          watchExtension.scheduleBackgroundRefresh(withPreferredDate: targetDate, userInfo: nil) {
              error in
      
              // Check for errors.
              if let error {
                  scheduleLogger.error(
                      "An error occurred while scheduling a background refresh task: \(error.localizedDescription)"
                  )
                  return
              }
      
              scheduleLogger.debug("Task scheduled!")
          }
      }
    • 22:15 - Revisiting the Recaffeinater

      //This class is guaranteed on the main actor...
      @MainActor
      class Recaffeinater: ObservableObject {
          @Published var recaffeinate: Bool = false
          var minimumCaffeine: Double = 0.0
      }
      
      //...but this protocol is not
      //warning: Main actor-isolated instance method 'caffeineLevel(at:)' cannot be used to satisfy nonisolated protocol requirement
      extension Recaffeinater: CaffeineThresholdDelegate {
          public func caffeineLevel(at level: Double) {
              if level < minimumCaffeine {
                  // TODO: alert user to drink more coffee!
              }
          }
      }
    • 22:26 - Option 1: Mark function as nonisolated

      //error: Main actor-isolated property 'minimumCaffeine' can not be referenced from a non-isolated context
      nonisolated public func caffeineLevel(at level: Double) {
          if level < minimumCaffeine {
              // TODO: alert user to drink more coffee!
          }
      }
    • 23:07 - Option 1b: Wrap functionality in a Task

      nonisolated public func caffeineLevel(at level: Double) {
          Task { @MainActor in
            if level < minimumCaffeine {
              // TODO: alert user to drink more coffee!
          	}
          }
      }
    • 23:34 - Option 1c: Explore options to update the protocol

      public protocol CaffeineThresholdDelegate: AnyObject {
          func caffeineLevel(at level: Double)
      }
    • 24:15 - Option 1d: Instead of wrapping it in a Task, use `MainActor.assumeisolated`

      nonisolated public func caffeineLevel(at level: Double) {
          MainActor.assumeIsolated {
              if level < minimumCaffeine {
                  // TODO: alert user to drink more coffee!
              }
          }
      }
    • 25:21 - `@preconcurrency` as a shorthand for assumeIsolated

      extension Recaffeinater: @preconcurrency CaffeineThresholdDelegate {
          public func caffeineLevel(at level: Double) {
              if level < minimumCaffeine {
                  // TODO: alert user to drink more coffee!
              }
          }
      }
    • 26:42 - Add `@MainActor` to the delegate protocol in CoffeeKit

      @MainActor
      public protocol CaffeineThresholdDelegate: AnyObject {
          func caffeineLevel(at level: Double)
      }
    • 26:50 - A new warning

      //warning: @preconcurrency attribute on conformance to 'CaffeineThresholdDelegate' has no effect
      extension Recaffeinater: @preconcurrency CaffeineThresholdDelegate {
          public func caffeineLevel(at level: Double) {
              if level < minimumCaffeine {
                  // TODO: alert user to drink more coffee!
              }
          }
      }
    • 27:09 - Remove @preconcurrency

      extension Recaffeinater: CaffeineThresholdDelegate {
          public func caffeineLevel(at level: Double) {
              if level < minimumCaffeine {
                  // TODO: alert user to drink more coffee!
              }
          }
      }
    • 29:56 - Global variables in CoffeeKit are marked as `var`

      //warning: Var 'hkLogger' is not concurrency-safe because it is non-isolated global shared mutable state
      private var hkLogger = Logger(
          subsystem:
              "com.example.apple-samplecode.Coffee-Tracker.watchkitapp.watchkitextension.HealthKitController",
          category: "HealthKit")
      
      // The key used to save and load anchor objects from user defaults.
      //warning: Var 'anchorKey' is not concurrency-safe because it is non-isolated global shared mutable state
      private var anchorKey = "anchorKey"
      
      // The HealthKit store.
      // warning: Var 'store' is not concurrency-safe because it is non-isolated global shared mutable state
      private var store = HKHealthStore()
      // warning: Var 'isAvailable' is not concurrency-safe because it is non-isolated global shared mutable state
      private var isAvailable = HKHealthStore.isHealthDataAvailable()
      
      // Caffeine types, used to read and write caffeine samples.
      // warning: Var 'caffeineType' is not concurrency-safe because it is non-isolated global shared mutable state
      private var caffeineType = HKObjectType.quantityType(forIdentifier: .dietaryCaffeine)!
      // warning: Var 'types' is not concurrency-safe because it is non-isolated global shared mutable state
      private var types: Set<HKSampleType> = [caffeineType]
      
      // Milligram units.
      // warning: Var 'miligrams' is not concurrency-safe because it is non-isolated global shared mutable state
      internal var miligrams = HKUnit.gramUnit(with: .milli)
    • 30:19 - Change all global variables to `let`

      private let hkLogger = Logger(
          subsystem:
              "com.example.apple-samplecode.Coffee-Tracker.watchkitapp.watchkitextension.HealthKitController",
          category: "HealthKit")
      
      // The key used to save and load anchor objects from user defaults.
      private let anchorKey = "anchorKey"
      
      // The HealthKit store.
      private let store = HKHealthStore()
      private let isAvailable = HKHealthStore.isHealthDataAvailable()
      
      // Caffeine types, used to read and write caffeine samples.
      private let caffeineType = HKObjectType.quantityType(forIdentifier: .dietaryCaffeine)!
      private let types: Set<HKSampleType> = [caffeineType]
      
      // Milligram units.
      internal let miligrams = HKUnit.gramUnit(with: .milli)
    • 30:38 - Warning 1: Sending arrays in `drinksUpdated()`

      // warning: Sending 'self.currentDrinks' risks causing data races
      // Sending main actor-isolated 'self.currentDrinks' to actor-isolated instance method 'save' risks causing data races between actor-isolated and main actor-isolated uses
      await store.save(currentDrinks)
    • 32:04 - Looking at Drink struct

      // The record of a single drink.
      public struct Drink: Hashable, Codable {
      
          // The amount of caffeine in the drink.
          public let mgCaffeine: Double
      
          // The date when the drink was consumed.
          public let date: Date
      
          // A globally unique identifier for the drink.
          public let uuid: UUID
      
          public let type: DrinkType?
      
          public var latitude, longitude: Double?
      
          // The drink initializer.
          public init(type: DrinkType, onDate date: Date, uuid: UUID = UUID()) {
              self.mgCaffeine = type.mgCaffeinePerServing
              self.date = date
              self.uuid = uuid
              self.type = type
          }
      
          internal init(from sample: HKQuantitySample) {
              self.mgCaffeine = sample.quantity.doubleValue(for: miligrams)
              self.date = sample.startDate
              self.uuid = sample.uuid
              self.type = nil
          }
      
          // Calculate the amount of caffeine remaining at the provided time,
          // based on a 5-hour half life.
          public func caffeineRemaining(at targetDate: Date) -> Double {
              // Calculate the number of half-life time periods (5-hour increments)
              let intervals = targetDate.timeIntervalSince(date) / (60.0 * 60.0 * 5.0)
              return mgCaffeine * pow(0.5, intervals)
          }
      }
    • 33:29 - Mark `Drink` struct as Sendable

      // The record of a single drink.
      public struct Drink: Hashable, Codable, Sendable {
        //...
      }
    • 33:35 - Another type that isn't Sendable

      // warning: Stored property 'type' of 'Sendable'-conforming struct 'Drink' has non-sendable type 'DrinkType?'
      public let type: DrinkType?
    • 34:28 - Using nonisolated(unsafe)

      nonisolated(unsafe)
      public let type: DrinkType?
    • 34:45 - Undo that change

      public let type: DrinkType?
    • 35:04 - Change DrinkType to be Sendable

      // Define the types of drinks supported by Coffee Tracker.
      public enum DrinkType: Int, CaseIterable, Identifiable, Codable, Sendable {
        //...
      }
    • 36:35 - CoreLocation using AsyncSequence

      //Create a new drink to add to the array.
      var drink = Drink(type: type, onDate: date)
      
      do {
        //error: 'CLLocationUpdate' is only available in watchOS 10.0 or newer
        for try await update in CLLocationUpdate.liveUpdates() {
          guard let coord = update.location else {
            logger.info( "Update received but no location, \(update.location)")
            break
          }
          drink.latitude = coord.coordinate.latitude
          drink.longitude = coord.coordinate.longitude
        } catch {
          
        }
    • 38:10 - Create a CoffeeLocationDelegate

      class CoffeeLocationDelegate: NSObject, CLLocationManagerDelegate {
        var location: CLLocation?
        var manager: CLLocationManager!
        
        var latitude: CLLocationDegrees? { location?.coordinate.latitude } 
        var longitude: CLLocationDegrees? { location?.coordinate.longitude }
      
        override init () {
          super.init()
          manager = CLLocationManager()
          manager.delegate = self
          manager.startUpdatingLocation()
        }
        
        func locationManager (
          _ manager: CLLocationManager, 
          didUpdateLocations locations: [CLLocation]
        ) {
            self.location = locations. last
        }
      }
    • 39:32 - Put the CoffeeLocationDelegate on the main actor

      @MainActor
      class CoffeeLocationDelegate: NSObject, CLLocationManagerDelegate {
        var location: CLLocation?
        var manager: CLLocationManager!
        
        var latitude: CLLocationDegrees? { location?.coordinate.latitude } 
        var longitude: CLLocationDegrees? { location?.coordinate.longitude }
      
        override init () {
          super.init()
          // This CLLocationManager will be initialized on the main thread
          manager = CLLocationManager()
          manager.delegate = self
          manager.startUpdatingLocation()
        }
        
        // error: Main actor-isolated instance method 'locationManager_:didUpdateLocations:)' cannot be used to satisfy nonisolated protocol requirement
        func locationManager (
          _ manager: CLLocationManager, 
          didUpdateLocations locations: [CLLocation]
        ) {
            self.location = locations. last
        }
      }
    • 40:06 - Update the locationManager function

      nonisolated func locationManager (
        _ manager: CLLocationManager, 
        didUpdateLocations locations: [CLLocation]
      ) {
          MainActor.assumeIsolated { self.location = locations. last }
      }

Developer Footer

  • Vídeos
  • WWDC24
  • Migrate your app to Swift 6
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines