Swift Concurrencyについて調べる
こちらを参考に多方面から、キャッチアップしていく。
まず、WWDCのセッションでざっくり理解する。
koherさんの記事を参考に、WWDCセッションを見ていく。
Swift Concurency関連WWDCセッション
Actorとは
MainActorは、UIのようなメインスレッドで動くものと理解しているがあっているのか?
Viewプロトコルは、@MainActorがついており、メインスレッドで実行されることが保証される。
@MainActor @preconcurrency public protocol View {
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required ``View/body-swift.property`` property.
associatedtype Body : View
/// The content and behavior of the view.
///
/// When you implement a custom view, you must implement a computed
/// `body` property to provide the content for your view. Return a view
/// that's composed of built-in views that SwiftUI provides, plus other
/// composite views that you've already defined:
///
/// struct MyView: View {
/// var body: some View {
/// Text("Hello, World!")
/// }
/// }
///
/// For more information about composing views and a view hierarchy,
/// see <doc:Declaring-a-Custom-View>.
@ViewBuilder @MainActor @preconcurrency var body: Self.Body { get }
}
Actorは、よくわからないので調べる。
この記事の以下の箇所で、nonisolated
キーワードを理解した。
クラスや構造体に @MainActor がついていたとしても、nonisolated キーワードのついたメソッドやプロパティは MainActor から除外されます
特定のメソッドやプロパティのみに @MainActor を付与することもできます
SwiftZoomin #20を見る
こちらの内容を要約しながら理解する。
重要な登場人物
- Isolation domain
- Isolation boundary
- Sendable
Isolation domain
隔離=Isolation
する領域のことをIsolation domain
と呼ぶ。
Isolation domain
の重要な特性は、一つのIsolation domainのなかでは同時に一つの処理しか実行されないことである。
actor
というのは、一つの領域で隔離(Isolation)することでデータ競合を防いでいる。
最も馴染み深いのは、MainActor
のIsolation domain
であり、actor
のインスタンスに紐づいたIsolation domainも存在する。
MainActor
のIsolation domain
はMain Actor
で保護された領域を表す。
Actor
の場合は、Actor
のインスタンス事にIsolation domain
をもつ。同じActor
でもインスタンスごとにIsolation domain
を持つため、注意が必要。
Isolation boundary
Isolation domain
が複数存在しているときのそれぞれのIsolation domain
の間の境界のことをIsolation boundary
と呼ぶ。
アプリにおいては、それぞれのIsolation domain
間でIsolation boundary
を超えて情報のやり取りをしないといけない場面が多々ある。
Sendable
Sendable 並行にアクセスされても安全な型だけが準拠できるプロトコル
Sendable
の特徴は、Sendable
に準拠した型の値だけがIsolation boudnary
を超えられる。
non-Sendable
Sendable
に対して、non-Sendable
な値は、Isolation boundary
を超えることができない。
実行時にデータ競合が起こらないことを確認することをコンパイル時にチェックできるように、型の問題とすることで実現している。
Sendableを体感する
上記のBoxクラスをSendableに準拠させて、並行にアクセス可能にさせてみる。
final class Box: Sendable {
var value: Int = 0
}
そうすると、次のようにSendableに準拠すること自体がエラーであることがわかる。
Box型のプロパティValueが可変状態のため、並行にアクセスすると危険である旨のメッセージが出る。
Stored property 'value' of 'Sendable'-conforming class 'Box' is mutable
ここで、var
ではなく、let
だったらどうなるか。
final class Box: Sendable {
let value: Int = 0
}
Box
クラスのエラーは解消される。
value
プロパティが定数になるため、run()
メソッド内でbox.balue = -1
で書き込みをしていた箇所がコンパイルエラーになる。
この部分を読み込むだけのprint(box.value)
とすれば、データ競合を起こさない安全なコードにできる。データ競合は、並行にアクセスする少なくとも1つで書き換えが起こっているときに生じるものであるため、今回のように読み込むだけであれば、データ競合の危険性はない。
func run() {
let box: Box = .init()
Task {
// box.value = -1 // Cannot assign to property: 'value' is a 'let' constant
print(box.value)
}
Task {
print(box.value)
}
}
Isolation boundaryを体感する
Sendable
に準拠していないnon-Sendable
なBox
クラスがIsolation boundary
を超えるとコンパイルエラーが出る例を示す。
final class Box {
var value: Int = 0
}
actor A {
var box: Box?
func setBox(_ box: Box) {
self.box = box
}
}
func run() async {
let box: Box = .init()
let a: A = .init() // actor A のインスタンスa
await a.setBox(box) // actor A のインスタンスaにboxを渡す ⇐ boxがIsolation bounadaryを超えて、actor AのインスタンスaのIsolation domainに入る。
// ここで次のエラーメッセージが出る。
// Sending 'box' risks causing data races
print(box.value)
}