Sendable — その概要、スレッドセーフティプロトコル

著者: IT Sectr 公開日: 2026-06-18 読了時間: 10 分

Sendableは、スレッド間での受け渡しに安全な型をマークするSwiftのプロトコルです。並行処理(actor、async/await、Task)を扱う場合、Swiftコンパイラは、隔離されたコンテキスト間で受け渡しされるすべてのデータがSendableに準拠することを要求します。これにより、データ競合を引き起こす可能性のある安全でない型が誤って受け渡しされるのを防ぎます。このプロトコルは契約として機能します。Sendableに準拠した型は、内部に同期されていない状態がないことを保証します。WWDC 2021によると、Sendableプロトコルは安全なマルチスレッドアーキテクチャを設計する上で必須の要素です。

重要ポイント

  • Sendable — スレッド間の受け渡しにおけるデータ競合の不在を保証するプロトコル
  • 値型(struct、enum)は、すべてのプロパティがSendableであれば自動的にSendableに準拠します
  • @unchecked Sendable — 開発者が安全性の責任を負うクラス向けのバイパスメカニズム
  • Sendableクロージャは@Sendable属性でマークされ、変数のキャプチャをチェックします
  • コンパイラは、非Sendable型が隔離されたコンテキスト間で受け渡しされるとエラーを出力します

SwiftにおけるSendableプロトコルとは?

Sendableは、Swift標準ライブラリ(SE-0302)のプロトコルで、隔離ドメイン間での受け渡しに安全な型をマークします。隔離ドメインとは、actor、Task、@MainActorコンテキストを意味します。Sendableに要件はありません — セーフティマーカーとして機能します。

Sendableが必要な理由

Swift 5.5以前は、開発者はDispatchQueueを介して任意のオブジェクトをスレッド間で受け渡しでき、コンパイラはそれが安全かどうかをチェックしませんでした。Sendableはこのギャップを埋めます。今ではコンパイラ自体がスレッド間の転送を追跡し、安全でないものをブロックします。これにより、並行コードは実行前からより信頼性が高くなります。

swift
struct UserProfile: Sendable {
    let name: String
    let age: Int
}

class NonSendableClass {
    var counter: Int = 0
}

この例では、UserProfileはSendable型の定数プロパティを持つ構造体であるため、スレッド間で安全に受け渡しできます。NonSendableClassは、awaitを介してまたはTaskに受け渡そうとするとコンパイルエラーになります。

デフォルトでSendableに準拠する型

SwiftのSendable型は3つのカテゴリに分類されます。Sendableプロパティを持つ値型、不変状態のfinalクラス、@Sendableでマークされた関数/クロージャです。コンパイラは値型に対して自動的にSendable準拠を推論します。

値型と自動準拠

Struct、Enumおよびタプルは、すべてのプロパティと関連値がSendableであれば自動的にSendableになります。これは保守的なアプローチです。1つのプロパティでもSendableに準拠していない場合、型全体が安全として認識されません。

Sendable条件
Structはい(明示的な宣言なし)すべてのプロパティがSendable
Enumはい(明示的な宣言なし)すべての関連値がSendable
Final classはい(明示的な宣言あり)すべてのletプロパティがSendable、varなし
Non-final classいいえ継承のためSendable不可

基本型

すべてのSwift組み込み型 — Int、String、Double、Bool、Optional、Array、Dictionary、Set — はSendableに準拠しています。これにより、型の合成がデフォルトで安全になります。開発者はカスタムクラスにのみ注意する必要があります。

Sendableとクラス: @unchecked Sendable

@unchecked Sendableは、クラスがコンパイラのチェックをバイパスして明示的に自身をSendableと宣言できるメカニズムです。開発者はそのようなクラスのスレッドセーフティの責任を負います。これはObjective-Cブリッジや最適化された構造に役立ちます。

@unchecked Sendableを使用するタイミング

@unchecked Sendableは、クラスが内部的にロックやアトミック操作によって安全性を保証しているが、コンパイラが静的に検証できない場合に使用されます。たとえば、os_unfair_lockやpthread_mutex_tを持つクラス — そのスレッドセーフティはコードによって保証されていますが、Swiftはそれを認識しません。

swift
final class AtomicCounter: @unchecked Sendable {
    private var value: Int = 0
    private let lock = NSLock()
    
    func increment() {
        lock.lock()
        value += 1
        lock.unlock()
    }
}

@unchecked Sendableの使用は慎重に行う必要があります。これは他の開発者への明確なシグナルです。「確認済み、安全です」。実装のミスは発見が困難な競合を引き起こす可能性があります。@uncheckedを使用する前に、型が本当に値型として書き直せないことを確認してください。

Actorと組み合わせたSendable

ActorとSendableは表裏一体です。Actorはその状態を隔離しますが、外部の世界とデータを交換するにはSendable型を返す必要があります。Actorメソッドが非Sendable型を返す場合、コンパイラは警告またはエラーを発します。

Sendableを介したデータ転送

Actorが外部コードにデータを送信するとき、このデータは隔離境界を越えます。Sendableは、受信者がActor外でデータを安全に使用できることを保証します。Actor自体は隔離されたままです — その内部状態は公開されません。

swift
struct AccountSnapshot: Sendable {
    let id: UUID
    let balance: Double
    let lastUpdated: Date
}

actor BankActor {
    private var balance: Double = 0
    
    func snapshot() async -> AccountSnapshot {
        return AccountSnapshot(
            id: UUID(),
            balance: balance,
            lastUpdated: Date()
        )
    }
}

AccountSnapshotは、Sendable型のletプロパティのみを持つSendable構造体です。このアプローチはActorからデータを抽出するためのベストプラクティスです。状態のスナップショットは値で渡され、Actorはその状態の制御を失いません。

Sendable関数とクロージャ

@Sendableは、クロージャが非Sendableデータを可変形式でキャプチャしないことを保証する関数とクロージャの属性です。クロージャがTaskやActorメソッドに渡されるとき、それはSendableでなければなりません。

@Sendableクロージャでのキャプチャチェック

コンパイラは、@Sendableクロージャがクラスへの可変参照をキャプチャしないことをチェックします。Sendable型のletプロパティのキャプチャは許可されています。参照型のvar変数をキャプチャすると、クロージャがミューテーションと同時に実行される可能性があるため、エラーになります。

swift
func performAsync(operation: @Sendable () -> Void) {
    Task {
        await operation()
    }
}

let constant = "Safe"
var counter = 0
// counter — varをキャプチャするとcounterがミューテートされ、コンパイルエラー
performAsync { // ❌ キャプチャされた変数のミューテーション
    print(constant)
}

ルールはシンプルです。@Sendableクロージャは、それ自体がSendableであり、外部からミューテーションされないデータのみをキャプチャできます。クラスの場合、クラスがSendableとしてマークされていなければ、弱参照weak selfのキャプチャが許可されます。これにより、古典的な retain cycle や競合状態を防ぎます。

よくある質問

Sendableを簡単に言うと何ですか?

Sendableは、コンパイラに「この型はスレッド間で安全に受け渡しできます」と伝えるマーカーです。構造体や定数は通常デフォルトでSendableですが、クラスは明示的に指定しない限りそうではありません。

どの型が自動的にSendableに準拠しますか?

Sendableプロパティを持つ値型(struct、enum)、不変状態のfinalクラス、およびすべてのSwift基本型(Int、String、Double、Bool、Array、Dictionary、Optional)は自動的にSendableに準拠します。

@unchecked Sendableとは何ですか?

@unchecked Sendableは、コンパイラの静的チェックなしでクラスをSendableとして宣言する方法です。開発者はロックやアトミック操作などを通じて、自身で安全性を保証します。

なぜActorにSendableが必要なのですか?

Actorはその状態を隔離しますが、外部コードにデータを返すとき、このデータは隔離境界を越えます。Sendableは、受信者がActor外でこのデータを使用する際に競合に遭遇しないことを保証します。

SwiftでクラスをSendableにするには?

すべてのプロパティがSendable型の定数であるfinalクラスにSendableプロトコルを追加します。クラスがロックを使用する場合は@unchecked Sendableを使用できますが、注意が必要です。

まとめ

  • Sendable — スレッド間の安全なデータ転送のためのマーカープロトコル
  • 値型は条件を満たすと自動的にSendableに準拠します
  • クラスは明示的なSendableまたは@unchecked Sendableの宣言が必要です
  • ActorはSendable型を介してのみ外部コードにデータを返します
  • @Sendableクロージャ属性は変数キャプチャの正確性をチェックします
  • コンパイラは静的にSendable準拠をチェックし、ビルド時に競合を防ぎます

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

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

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

こちらもお読みください