Slide 1

Slide 1 text

メインスレッドをブロック させないための Swift Concurrency クイズ tokizo / @tokizuoh 2024/08/23 iOSDC Japan 2024 1

Slide 2

Slide 2 text

README ● tokizo ○ @tokizuoh ● 株式会社はてな ○ マンガアプリチーム 2

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

5 メインスレッドを 適切に使う

Slide 6

Slide 6 text

メインスレッドを適切に使う (1/2) ● 適切さ ○ メインスレッドで実行が求められる処理のみを メインスレッドで実行する ■ UIの更新など ○ メインスレッドでやらなくて良い処理を バックグラウンドスレッドに逃がす 6

Slide 7

Slide 7 text

メインスレッドを適切に使う (2/2) ● 適切に使わないと... ○ UIの応答性の低下 ○ クラッシュ ■ ユーザー体験の低下 ■ → ユーザー離脱・売上減少 ■ → ブランドイメージの低下 7

Slide 8

Slide 8 text

8 メインスレッドを適切に使っ てアプリの品質を高めたい

Slide 9

Slide 9 text

9 しかし Swift Concurrencyは 難しい

Slide 10

Slide 10 text

10 Swift Concurrencyの 難しさとは

Slide 11

Slide 11 text

11 Swift Concurrencyは 規則が多い

Slide 12

Slide 12 text

Swift Concurrencyは規則が多い (1/2) ● Actor, Global Actorなどの詳細な規則を知るには 以下を読む・追う必要がある ■ Swift Evolutionのプロポーザル ■ WWDCのセッション ■ https://developer.apple.com/documentation/ swift/concurrency/ 12

Slide 13

Slide 13 text

Swift Concurrencyは規則が多い (2/2) ● 追う必要性 ○ Swift のバージョンによって規則が変わる 13

Slide 14

Slide 14 text

14 完全に理解するのは大変

Slide 15

Slide 15 text

15 規則を理解していないと 意図せず メインスレッドを 使ってしまう

Slide 16

Slide 16 text

16 今回のトークの クイズを解いて 規則を知ろう

Slide 17

Slide 17 text

17 トークのゴール

Slide 18

Slide 18 text

トークのゴール (初級者) ● クイズを解くことで、メインスレッドを ブロックさせないために必要な規則や ドキュメントのありかを知ること ○ 頭の中にインデックスを貼る 18

Slide 19

Slide 19 text

トークのゴール (熟練者) ● 理解度の確認ができること 19

Slide 20

Slide 20 text

20 ソースコードの共有

Slide 21

Slide 21 text

ソースコードの共有 ● コードのみ(答え無し・解説無し) ○ https://tokizuoh.hatenablog.com/iosdc-japan-2024-quiz 21

Slide 22

Slide 22 text

22 クイズのルール

Slide 23

Slide 23 text

クイズのルール (1/2) ● Xcode 15.4 ○ Swift 5.10 23

Slide 24

Slide 24 text

クイズのルール (2/2) // ✅: 通る → MainActor上、つまりメインスレッドで実行される* // ❌: 通らない → MainActor上で実行されない MainActor.assertIsolated() 24 *: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md

Slide 25

Slide 25 text

25 練習

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

27 答え

Slide 28

Slide 28 text

import UIKit class PracticeViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() MainActor.assertIsolated() // ✅ } } 28

Slide 29

Slide 29 text

29 解説

Slide 30

Slide 30 text

練習: 解説 (1/3) ● UIViewController には @MainActor が付いている 30 https://developer.apple.com/documentation/uikit/uiviewcontroller

Slide 31

Slide 31 text

練習: 解説 (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 の隔離を 推論される**

Slide 32

Slide 32 text

練習: 解説 (3/3) ● よって、viewDidLoad() は MainActor 上で実行 される ○ → メインスレッドで実行される 32

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

34 正解の方󰢨

Slide 35

Slide 35 text

35 練習おわり

Slide 36

Slide 36 text

36 全5問

Slide 37

Slide 37 text

37 参ります

Slide 38

Slide 38 text

38 問1

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

40 答え

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

42 解説

Slide 43

Slide 43 text

問1: 解説 ● Detached Task は呼び出し側の Actor の 一部ではない、非同期に実行できる作業単位* ○ → MainActor とは別で実行されるため、 メインスレッドで実行されない 43 *: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Unstructured-Concurrency

Slide 44

Slide 44 text

44 import UIKit class Q1ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { MainActor.assertIsolated() // ❌ } } }

Slide 45

Slide 45 text

45 正解の方🙋

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

47 問2

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

49 答え

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

51 解説

Slide 52

Slide 52 text

問2: 解説 (1/3) ● in の前に @MainActor を付けると、 クロージャの処理が Main Actor で実行される* 52 https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md#closures

Slide 53

Slide 53 text

問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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

56 正解の方󰢨

Slide 57

Slide 57 text

57 問3

Slide 58

Slide 58 text

@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

Slide 59

Slide 59 text

59 答え

Slide 60

Slide 60 text

@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

Slide 61

Slide 61 text

61 解説

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

問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() /* ✅ */ } }

Slide 64

Slide 64 text

問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

Slide 65

Slide 65 text

@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

Slide 66

Slide 66 text

66 正解の方🙋

Slide 67

Slide 67 text

67 問4

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

69 答え

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

71 解説

Slide 72

Slide 72 text

問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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

74 正解の方󰢨

Slide 75

Slide 75 text

75 問5

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

77 答え

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

79 解説

Slide 80

Slide 80 text

問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

Slide 81

Slide 81 text

問5: 解説 (2/3) 81 https://github.com/swiftlang/swift-evolution/blob/main/proposals/0316-global-actors.md#global-actor-inference

Slide 82

Slide 82 text

● 問5のコードをよく見ると、await が無いので 同期メソッドを呼んでいるだけ class Q5ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() Task.detached { Y().f() } } } 問5: 解説 (3/3) 82

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

84 正解の方🙋

Slide 85

Slide 85 text

85 全5問 ありがとうございました!

Slide 86

Slide 86 text

クイズ まとめ ● 問1: SE-0304 Detached Task ● 問2: SE-0338 Actor 隔離されていない非同期関数 ● 問3: SE-0401 プロパティラッパーからの推論 ● 問4: SE-0316 Global Actorの規則 ● 問5: SE-0316 Global Actorの規則 86

Slide 87

Slide 87 text

87 何問解けましたか?

Slide 88

Slide 88 text

88 全問正解の方 🙋

Slide 89

Slide 89 text

89 拍手👏

Slide 90

Slide 90 text

90 メインスレッドを 適切に 使っていきましょう!