Thread.isMainThread の MainActor 版っぽいものがほしい(自分が今いる actor を調べる)
まとめ
func exampleForMainActor() {
// DEBUG ビルドのとき、
// ここが Main Actor のコンテキストで実行されていなければ
// ここでプログラムの実行が停止する
MainActor.assertIsolated()
// DEBUG ビルド・RELEASE ビルドの両方で、
// ここが Main Actor のコンテキストで実行されていなければ
// ここでプログラムの実行が停止する
MainActor.preconditionIsolated()
}
// `assertIsolated(_:file:line:)`・`preconditionIsolated(_:file:line:)` は
// `GlobalActor` の Type Methods なので、自作の Global Actor でも使える
@globalActor
actor BackgroundActor: GlobalActor { /* ... */ }
func exampleForBackgroundActor() {
BackgroundActor.assertIsolated()
BackgroundActor.preconditionIsolated()
}
// `Actor` の Instance Methods にも
// `assertIsolated(_:file:line:)`・`preconditionIsolated(_:file:line:)` がある
actor MyActor { /* ... */ }
let myActor = MyActor()
func exampleForMyActor() {
myActor.assertIsolated()
myActor.preconditionIsolated()
}
自分は今どの Global Actor のコンテキスト内にいるのか
Swift Concurrency において、非同期なメソッド等を呼び出す際に Task
を作ったり、SwiftUI であれば task(priority:_:)
などを使ったりします。
既存のコードたちを Swift Concurrency 向けに移行していく際、「今自分がいる Task
はどの actor のコンテキストを引き継いでいるのか?」を気にしたくなるときがあります。
Thread.isMainThread
を使うとコンパイル時に warning が出る
メインスレッドが求められていた状況を考えます。これまで、自分がいたスレッドがメインスレッドであるかどうかを知る手段の1つとして、Thread.isMainThread
を使うことがありました。
struct ContentView: View {
var body: some View {
Text("Hello, happy world!")
.task {
let isMainThread = Thread.isMainThread
assert(isMainThread) // DEBUG ビルドのとき、ここがメインスレッドで実行されていなければここでプログラムの実行が停止する
precondition(isMainThread) // DEBUG ビルド・RELEASE ビルドの両方で、ここがメインスレッドで実行されていなければここでプログラムの実行が停止する
}
}
}
上記のコード例の場合、SwiftUI の View
の body
には @MainActor
が付与されているため、Thread.isMainThread
は true
となります。
しかし、コンパイル時にはこのような warning が表示されていました。
Class property 'isMainThread' is unavailable from asynchronous contexts; Work intended for the main actor should be marked with @MainActor; this is an error in Swift 6
これをほぼ代替する仕組みが Swift 5.9(SE-0392)にて用意されました。
Apple プラットフォームでは iOS 17.0 以降、iPadOS 17.0 以降、macOS 14.0 以降、tvOS 17.0 以降、watchOS 10.0 以降、visionOS 1.0 以降で利用できる方法です。
assertIsolated(_:file:line:)
DEBUG 向けの まずは assert(isMainThread)
の方を見ます。ここで使われている assert(_:_:file:line:)
は、Swift コンパイラの最適化レベルが -Onone
(Xcode の DEBUG ビルドのデフォルト設定)のとき、condition
の値が false
であればそこでプログラムの実行が停止します。
これと同じ用途で用いることができるのが assertIsolated(_:file:line:)
です。
struct ContentView: View {
var body: some View {
Text("Hello, happy world!")
.task {
MainActor.assertIsolated() // DEBUG ビルドのとき、ここが Main Actor のコンテキストで実行されていなければここでプログラムの実行が停止する
}
}
}
preconditionIsolated(_:file:line:)
DEBUG・RELEASE 向けの 次に precondition(isMainThread)
の方を見ます。ここで使われている precondition(_:_:file:line:)
は、Swift コンパイラの最適化レベルが -Onone
(Xcode の DEBUG ビルドのデフォルト設定)や -O
(Xcode の RELEASE ビルドのデフォルト設定)のとき、condition
の値が false
であればそこでプログラムの実行が停止します。
これと同じ用途で用いることができるのが preconditionIsolated(_:file:line:)
です。
struct ContentView: View {
let queue = DispatchQueue.main
var body: some View {
Text("Hello, happy world!")
.task {
MainActor.preconditionIsolated() // DEBUG ビルド・RELEASE ビルドの両方で、ここが Main Actor のコンテキストで実行されていなければここでプログラムの実行が停止する
}
}
}
@MainActor
以外の Global Actor でも利用できる
assertIsolated(_:file:line:)
・preconditionIsolated(_:file:line:)
は MainActor
が適合している GlobalActor
に定義されています。
そのため、これまで Thread
などでは困難だった「今その Global Actor のコンテキスト内にいるか?」を調べることができます。
@globalActor
actor BackgroundActor: GlobalActor {
static let shared = BackgroundActor()
}
struct ContentView: View {
var body: some View {
Text("Hello, happy world!")
.task {
_ = await Task { @BackgroundActor in
BackgroundActor.assertIsolated() // ✅
BackgroundActor.preconditionIsolated() // ✅
}.value
_ = await Task {
BackgroundActor.assertIsolated() // 💥
BackgroundActor.preconditionIsolated() // 💥
}.value
}
}
}
Actor
でも使える
Global Actor ではない これまでは Global Actor の assertIsolated(_:file:line:)
・preconditionIsolated(_:file:line:)
を紹介しましたが、Actor
にも assertIsolated(_:file:line:)
・preconditionIsolated(_:file:line:)
が用意されています。
actor MyActor {}
struct ContentView: View {
let myActor = MyActor()
var body: some View {
Text("Hello, happy world!")
.task {
myActor.assertIsolated() // 💥
myActor.preconditionIsolated() // 💥
}
}
}
注意点
GlobalActor
の assertIsolated(_:file:line:)
・preconditionIsolated(_:file:line:)
、Actor
の assertIsolated(_:file:line:)
・preconditionIsolated(_:file:line:)
はいずれも Swift Concurrency への移行作業中に、コードが意図した actor のエグゼキュータで動作しているかどうかを保証する方法として提供されています。
本来はこれらのアサーションを使わず、actor のメソッドにしたり Global Actor を付与したりするのがベストです。
Discussion