Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Heart of Swift Concurrency

Avatar for Yuta Koshizawa Yuta Koshizawa
September 14, 2026

Heart of Swift Concurrency

Avatar for Yuta Koshizawa

Yuta Koshizawa

September 14, 2026

More Decks by Yuta Koshizawa

Other Decks in Programming

Transcript

  1. Isola&on Domainのカテゴリー all func&on and variable declara&ons have a well-defined

    sta&c isola&on domain. These domains will always fall into one of three categories: 1. Non-isolated 2. Isolated to an actor value 3. Isolated to a global actor 2 "Migra)ng to Swi/ 6" h2ps://www.swi/.org/migra)on/documenta)on/swi/-6-concurrency-migra)on-guide/ dataracesafety#Isola)on-Domains
  2. ActorのIsola*on Domainはインスタンスごとに存在 actor Counter { var count: Int = 0

    func countUp() { ... } func foo() async { let counter: Counter = .init() await counter.countUp() // awaitが必要 print(await counter.count) // awaitが必要 } }
  3. 自身のプロパティやメソッドには同期的にアクセス可 actor Counter { var count: Int = 0 func

    countUp() { ... } func countUpTwice() { countUp() // 同期 countUp() // 同期 } }
  4. Global ActorとIsola-on Domain @MainActor final class FooViewModel { var value:

    Int = 42 } // 暗黙的に@MainActor final class FooViewController: UIViewController { let viewModel: FooViewModel = .init() func buttonPressed() { print(viewModel.value) // 同期 } }
  5. ミュータブルクラス final class Foo: Sendable { // var value: Int

    // 可変 } init(value: Int) { self.value = value } 同時にvalueにアクセスされるとデータ競合の原因となり得る
  6. イミュータブルクラス final class Foo: Sendable { // let value: Int

    // 不変 } init(value: Int) { self.value = value } 可変状態を持たないのでデータ競合の原因とならない
  7. (純粋な)値型 struct Foo: Sendable { // var value: Int //

    可変でもOK } init(value: Int) { self.value = value } 渡すときにコピーされるのでそもそも共有されない ✅
  8. Actor actor Foo { // ✅ 自動的にSendable準拠 var value: Int

    // 可変でもOK } init(value: Int) { self.value = value } valueはSerial Executorで保護されている
  9. ロック等で可変状態が保護されたミュータブルクラス final class Foo: Sendable { // private let mutex:

    Mutex<Int> var value: Int { get { mutex.withLock { $0 } } set { mutex.withLock { $0 = newValue } } } } init(value: Int) { self.mutex = .init(value) }
  10. non-SendableがIsola(on Boundaryを越えていい例 let a: A = .init() let foo: Foo

    = .init() await a.accept(foo) // ✅ 作って渡すだけ
  11. non-SendableがIsola(on Boundaryを越えてはダメな例 let a: A = .init() let foo: Foo

    = .init() await a.accept(foo) // foo.value += 1 // ⚠ 越境してはいけない 後から使うとデータ競合を起こし得る ⛔
  12. 間接的に「作って渡すだけ」の場合 ⛔ await bar(Foo()) // 作って渡すだけ func bar(_ foo: Foo)

    async { // fooが呼び出し元でもう使用されないか判断できない } await a.accept(foo)
  13. 間接的に「作って渡すだけ」の場合 ✅ await bar(Foo()) // 作って渡すだけ func bar(_ foo: sending

    Foo) async { // fooが呼び出し元でもう使用されないとわかる } await a.accept(foo)
  14. AVCaptureVideoDataOutputはcallback queueを要求 actor CameraProcessor { private let output: AVCaptureVideoDataOutput private

    lazy var delegate: VideoOutputDelegate = ... } private func configure() throws { // ... output.setSampleBufferDelegate( delegate, queue: queue // DispatchQueue ) }
  15. Delegate nonisolated final class VideoOutputDelegate: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { private weak

    let owner: CameraProcessor? } init(owner: CameraProcessor) { self.owner = owner }
  16. DelegateメソッドはActorに隔離されていない func captureOutput( _ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from

    connection: AVCaptureConnection ) { // Call to actor-isolated instance method 'process' // in a synchronous nonisolated context owner?.process(sampleBuffer) // }
  17. callback queueをActorのSerial Executorにする actor CameraProcessor { private let queue =

    DispatchSerialQueue(label: "...") } nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() }
  18. 同じqueueをDelegateのcallback queueにする actor CameraProcessor { private let output: AVCaptureVideoDataOutput private

    lazy var delegate: VideoOutputDelegate = ... } private func configure() throws { // ... output.setSampleBufferDelegate( delegate, queue: queue // DispatchSerialQueue ) }
  19. コンパイラは同じqueue上でも認識できない func captureOutput( _ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from

    connection: AVCaptureConnection ) { // Call to actor-isolated instance method 'process' // in a synchronous nonisolated context owner?.process(sampleBuffer) // }
  20. assumeIsolatedだけでは解決しない func captureOutput( _ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from

    connection: AVCaptureConnection ) { owner?.assumeIsolated { isolatedOwner in // Sending 'sampleBuffer' risks // causing data races isolatedOwner.process(sampleBuffer) // } }
  21. 静的なIsola&onのチェックを局所的に外す func captureOutput( _ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from

    connection: AVCaptureConnection ) { nonisolated(unsafe) let sampleBuffer = sampleBuffer } owner?.assumeIsolated { isolatedOwner in isolatedOwner.process(sampleBuffer) // }
  22. Mutexで可変状態を保護する final class OldFatService: Sendable { // var value: Int

    { get { _value.withLock { $0 } } set { _value.withLock { $0 = newValue } } } private let _value: Mutex<Int> } // ...
  23. Mutexで可変状態を保護する import Synchronization final class OldFatService: Sendable { // var

    value: Int { get { _value.withLock { $0 } } set { _value.withLock { $0 = newValue } } } private let _value: Mutex<Int> } // ...
  24. OSAllocatedUnfairLock (iOS 16, 17) import os final class OldFatService: Sendable

    { // var value: Int { get { _value.withLock { $0 } } set { _value.withLock { $0 = newValue } } } private let _value: OSAllocatedUnfairLock<Int> } // ...
  25. @koher • Heart of Swi- 執筆 • Swi- Zoomin' 主催

    • Swi- Digest 運営 • ムゲンノゲーム 開発 • Swi- × RealityKitで3Dモデリング →
  26. What is the difference between isola2on domain and concurrency domain?

    I'm leaning toward standardizing on "concurrency domain" and upda7ng the migra7on guide to use that term instead of "isola7on 1 domain" — Holly Borla (Mar 2, 2025) 1 h$ps://github.com/swi3lang/swi3-migra9on-guide/issues/130#issuecomment-2692364765
  27. Swi$ 6のデフォルト struct FooView: View { @State private var model:

    FooModel = .init() } func loadFoo() async throws { try await model.load() // }
  28. NonisolatedNonsendingByDefault struct FooView: View { @State private var model: FooModel

    = .init() } func loadFoo() async throws { try await model.load() // }