@MainActor — Swift非同期コードにおける概念、適用方法、特徴

著者: IT Sectr 公開日: 2026-03-19 読了時間: 8 分

@MainActorは、メインスレッドでのコード実行を保証するSwift言語のグローバルアクターです。Apple Developer, 2024によると、@MainActorはUI操作時のメインスレッドへの切り替えを自動化し、開発者が手動でDispatchQueue.main.asyncを呼び出す必要をなくします。このアノテーションはSwift 5.5のasync/awaitシステムとともに登場しました。

重要ポイント

  • @MainActor — メインスレッドでの実行を保証するSwiftのグローバルアクター。
  • async/awaitシステム — @MainActorの基盤となる仕組み。
  • クラスアノテーションにより、すべてのメソッドが自動的にメインスレッドで実行されます。
  • DispatchQueue.mainとは異なり、@MainActorはコンパイラレベルでスレッドをチェックします。
  • UI更新 — iOS開発における@MainActorの主な適用領域。

@MainActorとは?

@MainActorは、アクターの特性とアプリケーションのメインスレッドでの実行保証を組み合わせたSwiftのグローバルアクターです。Swift 5.5でasync/awaitおよび構造化並行処理とともに導入されたSwift並行処理システムの一部です。このアノテーションにより、開発者は手動でのスレッド切り替えを気にする必要がなくなり、UIバグの数が減少します。

Swift Concurrencyにおける定義と位置づけ

Swiftのアクターは、その状態を分離し、1つのスレッドのみが変更できることを保証する参照型です。@MainActorは、その実行基盤がメインスレッドである特別なグローバルアクターです。@MainActorでマークされたコードは、バックグラウンドタスクから呼び出された場合でもメインスレッドで実行されます。

@MainActor以前は、開発者はDispatchQueue.main.asyncを使用して手動でメインスレッドに切り替えていました。これは頻繁なエラーの原因であり、切り替え忘れによるメインスレッド以外でのUI更新クラッシュを引き起こしていました。@MainActorはこの問題を型システムレベルで解決します。

作成の背景

iOSアプリケーションのバグの大部分はUIの安全性の問題、つまりバックグラウンドスレッドからのインターフェース更新に起因します。Appleは@MainActorをSwift Concurrencyに組み込むことで、メインスレッドへの切り替えを自動化しコンパイラでチェック可能にし、実行時エラーのクラス全体を排除しました。

@MainActorの仕組み

@MainActorの動作原理はSwift Concurrencyの実行システムに基づいています。スレッドが@MainActorでマークされた関数を呼び出すと、スケジューラは現在のエグゼキュータでそれを一時停止し、メインスレッドで再開します。コンパイラは呼び出し境界を追跡し、安全性を保証します。

メインスレッドエグゼキュータ

@MainActorの実行はMainActor.sharedによって管理されます。これはアプリケーションのメインスレッドに関連付けられたエグゼキュータです。非同期関数が@MainActorでマークされている場合、元のタスクがどのスレッドで開始されたかに関係なく、常にこのエグゼキュータで再開されます。

swift
import SwiftUI

class ViewModel: ObservableObject {
    @Published var items: [String] = []

    @MainActor
    func loadData() async {
        let result = await fetchRemoteData()
        items = result  // 安全に、MainActorがメインスレッドを保証します
    }
}

アクターコンテキストの継承

@MainActorでマークされた関数が別の非同期関数を呼び出す場合、デフォルトでアクターコンテキストを継承します。つまり、明示的に指定されない限り、すべてのネストされた呼び出しもメインスレッドで実行されます。コンパイラはこれを追跡し、一貫性のないクロージャを渡そうとするとエラーを発行します。

@MainActor vs DispatchQueue.main

@MainActorとDispatchQueue.mainの比較は、両方がメインスレッドでのコード実行という同じタスクを解決するものの、新しいメカニズムがより安全で便利とされる理由を理解するのに役立ちます。

型レベルでの安全性

@MainActorはコンパイラレベルでのチェックです。安全でないコンテキストから@MainActor関数を呼び出そうとすると、コンパイラが警告またはエラーを発行します。DispatchQueue.main.asyncは実行時の呼び出しであり、コードはコンパイルされますが、バックグラウンドスレッドからUIを更新しようとすると実行時にクラッシュする可能性があります。

パフォーマンスとオーバーヘッド

DispatchQueue.main.asyncは遅延実行される可能性のあるブロックをキューに追加します。@MainActorはasync/awaitとともに、不要なクロージャを作成せずに直接エグゼキュータを切り替えます。これによりオーバーヘッドが削減され、実行時間がより予測可能になります。

swift
// 古いアプローチ
DispatchQueue.main.async {
    self.updateUI()
}

// @MainActorを使用した新しいアプローチ
@MainActor
func updateUI() {
    // メインスレッドで実行されます
    self.label.text = "更新済み"
}
基準@MainActorDispatchQueue.main
チェックコンパイラ実行時
構文アノテーション(宣言的)呼び出し(命令的)
オーバーヘッド低い(エグゼキュータ切替)中程度(クロージャ+キュー)
テスタビリティ高い(MainActor.sharedを差し替え可能)低い(モック化が困難)

iOSプロジェクトでの@MainActorの使用

実際のiOSプロジェクトでは、@MainActorはViewModel層、SwiftUIビュー、UIKitコントローラで使用されます。アノテーションは個々のメソッドにも型全体にも適用できます。

クラスまたは構造体へのアノテーション

クラスを@MainActorでマークすると、すべてのメソッドとプロパティがメインスレッドでのみアクセス可能であることが保証されます。これはSwiftUIビューやObservableObjectクラスに特に便利です。classの前に@MainActorを追加するだけで、すべての@Publishedプロパティが安全に更新されます。

swift
@MainActor
final class UserListViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false

    func fetchUsers() async {
        isLoading = true
        users = await api.getUsers()
        isLoading = false
    }
}

レガシーコードのラッピング

スレッド切り替えが手動だった古いUIKitコードを扱う場合、明示的な切り替えにMainActor.runを使用できます。これはコードベース全体を書き換えずにSwift Concurrencyへ段階的に移行するのに便利です。

swift
await MainActor.run {
    self.tableView.reloadData()
}

@MainActorの制限

すべての利点にもかかわらず、@MainActorにはアプリケーションアーキテクチャを設計する際に考慮すべきいくつかの制限があります。適用範囲の境界を理解することで、誤った使用を防ぐことができます。

集中的な使用時のパフォーマンス

呼び出しチェーン全体が@MainActorでマークされている場合、重い処理はすべてメインスレッドで実行され、UIのフリーズを引き起こします。@MainActorはUI層のみにマークし、ビジネスロジックやネットワークリクエストはバックグラウンドアクターまたはグローバルエグゼキュータに任せることを推奨します。

一部のAPIとの非互換性

古いコールバックベースのAPI(async/awaitなしのURLSessionなど)はアクターコンテキストをサポートしていません。統合にはCheckedContinuationを使用したラッパーが必要です。また、@MainActorはperformSelector、target-action、その他の非非同期UIKitパターンと互換性がありません。

マルチスレッドデバッグ

@MainActorを使用したアプリケーションのデバッグでは、コンパイラが実行時ではなくビルド時に多くの競合状態を防止するため、レースコンディションの再現が困難です。ただし、これにより誤った安心感が生まれる可能性があります。共有ミュータブルオブジェクト(NSCacheや共有グローバル変数など)が@MainActorでマークされておらず、明示的な同期なしで使用される場合、誤った操作が依然として発生する可能性があります。

@MainActorのテスト

@MainActorはテストでの手動スレッド切り替えの必要性をなくすため、UIロジックのテストを大幅に簡素化します。ただし、単体テストやUIテストを作成する際には考慮すべき点があります。

MainActorを使用した単体テスト

XCTestでは、テスト環境が自動的にメインスレッドエグゼキュータを設定します。テストメソッドがメインスレッドで実行される場合、@MainActor関数の呼び出しに追加の設定は不要で、同じコンテキストで実行されます。バックグラウンドシナリオをテストするには、明示的な優先度とエグゼキュータを指定してTask内でMainActor.runを使用し、バックグラウンドからの呼び出しでコードが正しく動作することを個別に確認します。

一般的なアプローチは@MainActorを使用したViewModelのテストで、非同期操作後に@Publishedプロパティが正しく更新されることを確認します。アクターコンテキストの継承により、テスト内でawaitを呼び出すと、追加のDispatchQueue保証や手動コンテキスト切り替えなしでメインスレッドでの実行が保証され、テストの記述が簡素化されます。

リファクタリング時の分離確認

既存のコードをSwift Concurrencyにリファクタリングする際、コンパイラを通じて@MainActorの分離を確認します。@MainActorコンテキストから@MainActorなしの同期メソッドを呼び出すとエラーとしてマークされます。この特性を利用してプロジェクトを段階的にasync/awaitに移行します。ViewModel層を@MainActorとしてマークすると、コンパイラがバックグラウンドアクターに移動する必要がある安全でない呼び出しをすべて強調表示します。

モックとアクターコンテキスト

@MainActor依存関係のモックを作成する際、戻り値の型を持つ非同期関数を宣言するasyncメソッドを持つプロトコルを使用します。これにより、アクターの分離を壊すことなくネットワークサービス、データベース、その他の外部依存関係を置き換えることができます。コンパイラはモックがすべての分離要件を実装していることを確認し、バックグラウンドテストスレッドから@MainActorコードへの偶発的なアクセスを防ぎます。

非同期操作の待機

@MainActorコードを同期的にテストする場合、XCTestExpectationを使用して非同期操作の完了を待ちます。テストで期待値を設定し、メインスレッドで実行されるクロージャ内でfulfillmentを呼び出します。テストが無限にハングする場合は、メインスレッドでの呼び出しが発生していない可能性が高く、アクターの分離を確認する必要があります。実行コンテキストをデバッグするには、テストコード内にThread.isMainThreadチェックを追加すると便利です。

よくある質問

クラス全体を@MainActorでマークする必要がありますか?

いいえ、UIを更新するメソッドのみをマークすれば十分です。ただし、クラスにそのようなメソッドが複数ある場合は、クラス全体に@MainActorを追加する方が簡単です。これにより、すべてのメンバーがメインスレッドで実行されることが保証され、コードの保守が簡素化されます。

@MainActorと@globalActorの違いは何ですか?

@MainActorはメインスレッドに結びついたグローバルアクターの特定のインスタンスです。@globalActorは独自のグローバルアクターを作成するためのプロトコルです。たとえば、プロジェクトのアーキテクチャで必要であれば、バックグラウンドスレッドでコードを実行する@BackgroundActorを作成できます。

@MainActorはasync/awaitなしで使用できますか?

はい、@MainActorを使用した同期関数もメインスレッドで実行されます。ただし、@MainActorの主な価値はasync/awaitとともに発揮され、非同期関数がDispatchQueue.mainを介した手動切り替えなしで自動的にメインスレッドで再開されます。

@MainActorタスクをキャンセルするには?

Task.cancel()は通常のタスクと同様に@MainActorタスクでも機能します。@MainActorタスクはTask.isCancelledを確認するか、CancellationErrorをスローできます。キャンセル時、メインスレッドはブロックされず、タスクは最も近い一時停止ポイントで実行を停止します。

@MainActorをバックグラウンドスレッドから呼び出すとどうなりますか?

コンパイラが安全性を保証します。バックグラウンドコンテキストから@MainActor関数を呼び出すと、コンパイラがエラーを指摘します。非同期呼び出しの場合は、呼び出し元コードにawaitをマークするだけでエグゼキュータがメインスレッドに切り替わります。同期呼び出しの場合は、MainActor.runによる明示的な切り替えが必要です。

まとめ

  • @MainActor — メインスレッドでの実行を保証するSwiftのグローバルアクター。
  • コンパイラチェックにより、UI安全性エラーのクラス全体が排除されます。
  • クラス全体へのアノテーションで、すべてのメソッドが自動的にメインスレッドで実行されます。
  • MainActor.run — レガシーコードと同期コンテキストのための明示的な切り替え。
  • DispatchQueue.mainとは異なり、@MainActorはクロージャを作成せず、エグゼキュータ切り替えを使用します。
  • 重い計算はUIのフリーズを避けるため@MainActor下で実行すべきではありません。
  • アクターコンテキストの継承により、非同期呼び出しのチェーンが簡素化され、コードが一貫性、予測可能性、UI安全性を備えます。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください