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

メインスレッドをブロックさせないためのSwift Concurrencyクイズ

tokizo
August 23, 2024
2.7k

メインスレッドをブロックさせないためのSwift Concurrencyクイズ

tokizo

August 23, 2024
Tweet

Transcript

  1. Swift Concurrencyは規則が多い (1/2) • Actor, Global Actorなどの詳細な規則を知るには 以下を読む・追う必要がある ▪ Swift

    Evolutionのプロポーザル ▪ WWDCのセッション ▪ https://developer.apple.com/documentation/ swift/concurrency/ 12
  2. クイズのルール (2/2) // ✅: 通る → MainActor上、つまりメインスレッドで実行される* // ❌: 通らない

    → MainActor上で実行されない MainActor.assertIsolated() 24 *: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md
  3. import UIKit class PracticeViewController: UIViewController { override func viewDidLoad() {

    super.viewDidLoad() MainActor.assertIsolated() // ✅ or ❌ ? } } 26
  4. import UIKit class PracticeViewController: UIViewController { override func viewDidLoad() {

    super.viewDidLoad() MainActor.assertIsolated() // ✅ } } 28
  5. 練習: 解説 (2/3) 31 *: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md#using-global-actors-on-a-type **: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md#global-actor-inference • クラスに

    @MainActor を付けた場合、メソッド、 プロパティ、サブスクリプトは MainActor に 隔離される* • サブクラスはスーパークラスから Actor の隔離を 推論される**
  6. import UIKit class PracticeViewController: UIViewController { override func viewDidLoad() {

    super.viewDidLoad() MainActor.assertIsolated() // ✅ } } 33
  7. import UIKit class Q1ViewController: UIViewController { override func viewDidLoad() {

    super.viewDidLoad() Task.detached { MainActor.assertIsolated() // ✅ or ❌ ? } } } 39
  8. 41 import UIKit class Q1ViewController: UIViewController { override func viewDidLoad()

    { super.viewDidLoad() Task.detached { MainActor.assertIsolated() // ❌ } } }
  9. 問1: 解説 • Detached Task は呼び出し側の Actor の 一部ではない、非同期に実行できる作業単位* ◦

    → MainActor とは別で実行されるため、 メインスレッドで実行されない 43 *: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Unstructured-Concurrency
  10. 44 import UIKit class Q1ViewController: UIViewController { override func viewDidLoad()

    { super.viewDidLoad() Task.detached { MainActor.assertIsolated() // ❌ } } }
  11. import UIKit // 以後省略 class Q1ViewController: UIViewController { override func

    viewDidLoad() { super.viewDidLoad() Task.detached { MainActor.assertIsolated() // ❌ } } } 46
  12. func foo() async { MainActor.assertIsolated() // ✅ or ❌ ?

    } class Q2ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { @MainActor in await foo() } } } 48
  13. func foo() async { MainActor.assertIsolated() // ❌ } class Q2ViewController:

    UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { @MainActor in await foo() } } } 50
  14. 問2: 解説 (1/3) • in の前に @MainActor を付けると、 クロージャの処理が Main

    Actor で実行される* 52 https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md#closures
  15. 問2: 解説 (2/3) • await foo() は MainActor 上で呼び出される •

    しかし、foo() 自体はどの Actor にも 隔離されていない async function のため、 中身の処理は MainActor 上で実行されない* 53 *: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0338-clarify-execution-non-actor-async.md#detailed-design
  16. @MainActor func foo() async { MainActor.assertIsolated() // ✅ } •

    関数に @MainActor を付けると通る 問2: 解説 (3/3) 54
  17. func foo() async { MainActor.assertIsolated() // ❌ } class Q2ViewController:

    UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { @MainActor in await foo() } } } 55
  18. @propertyWrapper struct AlwaysZero { @MainActor var wrappedValue: Int { return

    0 } } struct Counter { @AlwaysZero var value: Int func f() { MainActor.assertIsolated() /* ✅ or ❌ ? */ } } class Q3ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { await Counter().f() } } } 58
  19. @propertyWrapper struct AlwaysZero { @MainActor var wrappedValue: Int { return

    0 } } struct Counter { @AlwaysZero var value: Int func f() { MainActor.assertIsolated() /* ✅ */ } } class Q3ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { await Counter().f() } } } 60
  20. 問3: 解説 (1/3) • Global Actor が付与された wrappedValue を持つ プロパティラッパーが付与されたプロパティを持つ

    構造体やクラスはそのプロパティラッパーから Actor の隔離が推論される* 62 *: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md#global-actor-inference
  21. 問3: 解説 (2/3) • → @AlwaysZero が付与されたプロパティを持つ Counter は MainActor

    に隔離される 63 @propertyWrapper struct AlwaysZero { @MainActor var wrappedValue: Int { return 0 } } struct Counter { @AlwaysZero var value: Int func f() { MainActor.assertIsolated() /* ✅ */ } }
  22. 問3: 解説 (3/3) • Swift 6からは無効になる* ◦ Swift 5.9以降 Upcoming

    Feature Flag が使える ▪ DisableOutwardActorInference 64 *: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0401-remove-property-wrapper-isolation.md
  23. @propertyWrapper struct AlwaysZero { @MainActor var wrappedValue: Int { return

    0 } } struct Counter { @AlwaysZero var value: Int func f() { MainActor.assertIsolated() /* ✅ */ } } class Q3ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { await Counter().f() } } } 65
  24. @MainActor class Animal { deinit { MainActor.assertIsolated() // ✅ or

    ❌ ? } } class Q4ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { await Animal() } } } 68
  25. @MainActor class Animal { deinit { MainActor.assertIsolated() // ❌ }

    } class Q4ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { await Animal() } } } 70
  26. 問4: 解説 • deinit は Global Actor に隔離されない* • 2024/08/21現在、isolated

    deinit の プロポーザル** が Accept されている*** 72 *: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md#detailed-design **: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0371-isolated-synchronous-deinit.md ***: https://forums.swift.org/t/accepted-with-modifications-se-0371-isolated-synchronous-deinit/74042
  27. @MainActor class Animal { deinit { MainActor.assertIsolated() // ❌ }

    } class Q4ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { await Animal() } } } 73
  28. protocol P { @MainActor func f() } struct Y: P

    {} extension Y { func f() { MainActor.assertIsolated() /* ✅ or ❌ ? */ } } class Q5ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { Y().f() } } } 76
  29. protocol P { @MainActor func f() } struct Y: P

    {} extension Y { func f() { MainActor.assertIsolated() /* ❌ */ } } class Q5ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { Y().f() } } } 78
  30. 問5: 解説 (1/3) • SE-0316* 内のコードをほぼそのまま使用 • プロトコルの実装を準拠とは別に extension で

    行うと、そのプロトコルが要求する Global Actor を 引き継がない 80 *: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md#global-actor-inference
  31. protocol P { @MainActor func f() } struct Y: P

    {} extension Y { func f() { MainActor.assertIsolated() /* ❌ */ } } class Q5ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { Y().f() } } } 83
  32. クイズ まとめ • 問1: SE-0304 Detached Task • 問2: SE-0338

    Actor 隔離されていない非同期関数 • 問3: SE-0401 プロパティラッパーからの推論 • 問4: SE-0316 Global Actorの規則 • 問5: SE-0316 Global Actorの規則 86