axiom-assume-isolated について
現在の隔離ドメインが正しいと分かっている場合に、Swift の `assumeIsolated` でアクター隔離状態へ同期アクセスする方法を解説します。適切な用途、隔離が誤っている場合のクラッシュ、`await` などの代替手段を扱います。
主な機能
- 01アクター隔離状態への同期アクセス
- 02テストでの MainActor.assumeIsolated の使用
- 03デリゲートコールバックへの assumeIsolated の適用
- 04隔離チェックとクラッシュ動作の理解
- 05隔離が不明な場合の非同期手段の選択
活用シーン
- 01MainActor コードの同期テスト
- 02メインスレッド実行が保証されたデリゲートコールバックの処理
- 03同期コードでの非同期切り替えの回避
- 04安全でない隔離前提の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-assume-isolatedインストール後、Claude Code で/axiom-assume-isolatedを入力して読み込まれたか確認します。
詳細
assumeIsolated — アクター分離状態への同期アクセス
正しい分離ドメイン上にすでにいると確信できる場合に、アクター分離された状態に同期的にアクセスします。
使用する場面
✅ 使用する場面:
- MainActor のコードを同期的にテストする場合(Task のオーバーヘッドを回避)
- メインスレッドで実行されることがドキュメントに記載されているレガシーデリゲートコールバック
- async ホップのオーバーヘッドを避ける必要があるパフォーマンス重視のコード
- 特定のアクター上でコールバックが実行されることが保証されているプロトコル準拠
❌ 使用しない場面:
- 現在の分離状態が不明な場合(代わりに
awaitを使用) - すでに async コンテキストにいる場合(分離状態が確保されています)
- アクターをまたぐ呼び出しが必要な場合(async を使用)
- コールバックの発生元が不明、または信頼できない場合
API リファレンス
MainActor.assumeIsolated
static func assumeIsolated<T>(
_ operation: @MainActor () throws -> T,
file: StaticString = #fileID,
line: UInt = #line
) rethrows -> T where T: Sendable
動作:同期的に実行します。MainActor の直列エグゼキューター上にいない場合はクラッシュします。
カスタムアクターの assumeIsolated
func assumeIsolated<T>(
_ operation: (isolated Self) throws -> T,
file: StaticString = #fileID,
line: UInt = #line
) rethrows -> T where T: Sendable
Task と assumeIsolated の比較
| 項目 | Task { @MainActor in } | MainActor.assumeIsolated |
|---|---|---|
| 実行タイミング | 遅延(次のランループ) | 同期(インライン) |
| Async のサポート | あり(await を使用可能) | なし(同期のみ) |
| コンテキスト | あらゆるコンテキストから | 同期関数である必要がある |
| 失敗時の動作 | そのまま実行 | 分離状態が誤っている場合はクラッシュ |
| 使用目的 | 非同期処理を開始する | 分離状態を確認して、分離された状態にアクセスする |
パターン
パターン 1:MainActor のコードをテストする
@Test func viewModelUpdates() {
MainActor.assumeIsolated {
let vm = ViewModel()
vm.update()
#expect(vm.state == .updated)
}
}
パターン 2:レガシーデリゲートコールバック
WWDC 2024-10169 より — ドキュメントでメインスレッドへの配信が保証されている場合:
@MainActor
class LocationDelegate: NSObject, CLLocationManagerDelegate {
var location: CLLocation?
// CLLocationManager created on main thread delivers callbacks on main thread
nonisolated func locationManager(
_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]
) {
MainActor.assumeIsolated {
self.location = locations.last
}
}
}
パターン 3:@preconcurrency の省略形
@preconcurrency は省略形として同等の機能を提供し、自動的に assumeIsolated でラップします。
// ❌ Manual approach (verbose)
extension MyClass: SomeDelegate {
nonisolated func callback() {
MainActor.assumeIsolated {
self.updateUI()
}
}
}
// ✅ Using @preconcurrency (equivalent, cleaner)
extension MyClass: @preconcurrency SomeDelegate {
func callback() {
self.updateUI() // Compiler wraps in assumeIsolated
}
}
プロトコルに分離状態が追加された場合:@preconcurrency は不要になり、コンパイラが警告を表示します。
パターン 4:assumeIsolated の前にスレッドを確認する
呼び出し元のコンテキストが不明な場合(例:ライブラリコード):
func getView() -> UIView {
if Thread.isMainThread {
return createHostingViewOnMain()
} else {
return DispatchQueue.main.sync {
createHostingViewOnMain()
}
}
}
private func createHostingViewOnMain() -> UIView {
MainActor.assumeIsolated {
let hosting = UIHostingController(rootView: MyView())
return hosting.view
}
}
パターン 5:カスタムアクターへのアクセス
actor DataStore {
var cache: [String: Data] = [:]
nonisolated func synchronousRead(key: String) -> Data? {
// Only safe if called from DataStore's executor
assumeIsolated { isolated in
isolated.cache[key]
}
}
}
よくある間違い
間違い 1:コンパイラエラーを無視する
// ❌ DANGEROUS: Using assumeIsolated to silence warnings
func unknownContext() {
MainActor.assumeIsolated {
updateUI() // Crashes if not actually on main actor!
}
}
// ✅ When uncertain, use proper async
func unknownContext() async {
await MainActor.run {
updateUI()
}
}
間違い 2:GCD のメインキューと MainActor が同じだと思い込む
通常は同じですが、保証されているわけではありません。ドキュメントを確認するか、async を使用してください。
間違い 3:Async コンテキストで使用する
// ❌ Unnecessary — you already have isolation
@MainActor
func updateState() async {
MainActor.assumeIsolated { // Pointless
self.state = .ready
}
}
// ✅ Direct access
@MainActor
func updateState() async {
self.state = .ready
}
@preconcurrency が不要になる場合
プロトコルに後から MainActor の分離状態が追加された場合:
// Library update:
@MainActor
protocol CaffeineThresholdDelegate: AnyObject {
func caffeineLevel(at level: Double)
}
// Your code — @preconcurrency now warns:
// "@preconcurrency attribute on conformance has no effect"
extension Recaffeinater: CaffeineThresholdDelegate {
func caffeineLevel(at level: Double) {
// Direct access, no wrapper needed
}
}
クラッシュ時の動作
Apple のドキュメントより:
「現在のコンテキストがアクターの直列エグゼキューター上で実行されていない場合、このメソッドは致命的なエラーでクラッシュします。」
トラップは意図的なものです:競合状態によってユーザーデータが破損するより、クラッシュするほうが適切です。
リソース
WWDC:2024-10169
ドキュメント:/swift/mainactor/assumeisolated、/swift/actor/assumeisolated
スキル:axiom-swift-concurrency