SwiftのSendableを完全理解するための10のステップ – Japanシーモア

SwiftのSendableを完全理解するための10のステップ

SwiftのSendableプロトコルを詳細に学ぶイメージSwift
この記事は約14分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

このサービスは複数のSSPによる協力の下、運営されています。

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

近年、Swiftは急速な進化を続けており、その中でも同時実行性に関する新しいプロトコルや特性が注目を浴びています。

その中でキーワードとして挙がるのが、Sendableプロトコルです。

この記事では、SwiftにおけるSendableプロトコルの詳細から、その使い方、応用例、カスタマイズ方法、そして注意点までを順を追って解説していきます。

この記事を読むことで、Sendableプロトコルの背後にある思想から、具体的な活用方法までを深く理解することができます。

Swiftの最新の特性を活用して、より安全で効率的なコードを書くための一助として、ぜひ本記事をお役立てください。

●SwiftとSendableプロトコルとは?

SwiftはAppleが開発したプログラミング言語で、iOS、macOS、watchOS、tvOSなどのプラットフォームでのアプリ開発に広く用いられています。

Swiftはその名の通り、「迅速」を意味する言語であり、高性能で安全性を重視した言語設計が特徴です。

○Swiftの同時実行性の重要性

近年、マルチコアプロセッサが一般的となり、複数の処理を同時に行うことの重要性が増しています。

同時実行性を持つことで、アプリのレスポンス向上やリソースの効率的な使用が可能となります。

しかし、同時実行を行う上での難しさやリスクも増加します。例えば、データの同時アクセスによる競合や、意図しない状態の変更などが挙げられます。

Swiftでは、この同時実行性をより安全に、そして効率的に扱うための機能が数多く提供されています。

その中でも、Sendableプロトコルは非常に重要な役割を果たしています。

○Sendableプロトコルの登場背景

Swiftの同時実行性を支える中核的なプロトコルとしてSendableが導入されました。

これは、データの安全な転送や共有を担保するためのプロトコルです。

従来、複数のスレッドやタスク間でのデータのアクセスは、データの不整合やアクセス競合といったリスクが常に伴っていました。

そのリスクを最小限に抑え、安全にデータを扱うための仕組みが必要とされていたのです。

Sendableプロトコルの導入により、Swiftではデータの安全な転送や共有が容易になりました。

このプロトコルを適切に使用することで、開発者はデータの競合や不整合を回避し、安全かつ効率的な同時実行のコードを書くことが可能となります。

●Sendableの基本

Swiftの同時実行性を実現するための新機能として、Swift5.5から導入されたSendableプロトコル。

これは、データの非同時実行性を保証するためのものであり、データの安全性を高める役割を持っています。

○Sendableプロトコルの定義と役割

Sendableプロトコルは、Swiftの同時実行性をサポートするための新しいプロトコルとして導入されました。

このプロトコルを適用することで、そのデータが複数のスレッドやタスク間で安全に送信されることを保証することができます。

要するに、Sendableを採用することで、データの競合やデータの不整合を防ぐことが期待できるのです。

□非同時実行性とは?

非同時実行性とは、データが複数のスレッドやタスク間で同時にアクセスされることなく、1つのスレッドやタスクで一度にアクセスされる性質を指します。

これにより、データの更新やアクセスが同時に行われることによるデータの破損や不整合を防ぐことができます。

□Sendableの意義

Sendableプロトコルが導入される前、Swiftではデータの非同時実行性を保証する手段が不足していました。

しかし、Sendableの登場により、明示的に非同時実行性を要求することが可能となりました。

これにより、開発者はデータの安全性を確保しつつ、より高度な同時実行処理を実装することができるようになったのです。

●Sendableの使い方

Swiftの並行処理を安全に行うためには、Sendableプロトコルの理解が欠かせません。

このプロトコルを適切に使用することで、データを異なる実行コンテキスト間で安全に移動させることができます。

ここでは、Sendableの基本的な使用方法から、より進んだ使い方についてサンプルコードとともに説明します。

○サンプルコード1:基本的なSendableの適用

SwiftでのSendableプロトコルの基本的な適用方法を見ていきましょう。

struct User: Sendable {
    let id: Int
    let name: String
}

このコードでは、Userという構造体を定義し、Sendableプロトコルを採用しています。

この例では、User構造体がSendableプロトコルを採用することで、安全に異なる実行コンテキスト間でデータを移動させることが可能となります。

実際にこのUser構造体を異なる実行コンテキストで使用する場合、データの不整合や競合が起きることなく、安全にデータの移動ができることが期待されます。

○サンプルコード2:クラスや構造体にSendableを適用する

Sendableプロトコルは、構造体だけでなく、クラスにも適用することができます。

しかし、クラスの場合は少し注意が必要です。

class Item: Sendable {
    let code: String
    let description: String
    init(code: String, description: String) {
        self.code = code
        self.description = description
    }
}

このコードでは、ItemというクラスにSendableプロトコルを適用しています。

この例では、ItemクラスがSendableプロトコルを採用することで、同様に異なる実行コンテキスト間でのデータの移動が安全になります。

ただし、参照型であるクラスの場合、メモリ管理やスレッドセーフな実装に注意が必要です。

○サンプルコード3:非同時実行性を持たせるための制約

Sendableプロトコルを適用することで非同時実行性を持たせることができますが、実際にその性質を活かすためには、特定の制約を持つことが多いです。

struct Transaction: Sendable {
    var amount: Double
    let date: Date
    private(set) var isCompleted = false

    mutating func completeTransaction() {
        isCompleted = true
    }
}

このコードでは、Transaction構造体がSendableプロトコルを採用しています。

この例では、Transaction構造体において、isCompletedプロパティはプライベートセットとして宣言されており、外部からの不適切な変更を防ぐことができます。

このように、特定の制約を設けることで、非同時実行性の利点を最大限に活かすことができます。

このTransaction構造体を使用する場合、例えば取引が完了したかどうかを確認することなく、外部からisCompletedを変更することはできません。

これにより、データの一貫性を保ちつつ、安全に異なる実行コンテキスト間でデータを移動させることができます。

●Sendableの応用例

SwiftのSendableプロトコルを理解して基本的な使い方をマスターしたら、次はこのプロトコルの応用例を見ていきましょう。

実際の開発シーンでSendableをどのように活用できるのか、以下のサンプルコードとともに詳しく見ていきます。

○サンプルコード4:Sendableを活用したデータの安全な転送

Sendableを用いることで、データを安全に複数のスレッドやタスク間で転送することができます。

下記のコードでは、Sendableプロトコルを適用したデータを複数のタスクで安全に共有しています。

struct User: Sendable {
    var name: String
    var age: Int
}

// 何らかの非同時実行タスク
async {
    let user = User(name: "Taro", age: 30)
    await sendData(user)
}

// データを受け取る非同時実行タスク
func sendData(_ user: User) async {
    // ここでuserデータを安全に処理
    print("ユーザー名: \(user.name), 年齢: \(user.age)")
}

このコードでは、Userという構造体を定義してSendableプロトコルに準拠させています。

これにより、非同時実行タスク間でこのUserデータを安全に転送することが可能となります。

この例では、データの転送と受け取りを非同時実行タスクで行っています。

○サンプルコード5:Sendableとアクター(Actor)の組み合わせ

Swiftにおける同時実行の新しい概念として「アクター(Actor)」が導入されました。

Sendableとアクターを組み合わせることで、データの安全性をさらに高めることができます。

下記のコードでは、アクター内でSendableなデータを扱う方法を表しています。

actor ScoreManager {
    private var scores: [Int] = []

    func addScore(_ score: Int) async {
        scores.append(score)
    }

    func averageScore() async -> Double {
        let total = scores.reduce(0, +)
        return Double(total) / Double(scores.count)
    }
}

struct Score: Sendable {
    let value: Int
}

async {
    let manager = ScoreManager()
    let score = Score(value: 85)
    await manager.addScore(score.value)

    let average = await manager.averageScore()
    print("平均スコア: \(average)")
}

このコードでは、スコアを管理するScoreManagerというアクターを定義しています。

そして、ScoreというSendableな構造体を用意して、このスコアデータをアクターに安全に渡しています。

アクター内では、スコアの追加や平均スコアの計算を行っています。

この例では、アクター内部のスコアデータは外部から直接アクセスすることができないため、データの安全性が確保されています。

また、SendableなScoreデータをアクターに転送することで、データの一貫性も保たれています。

●注意点と対処法

SwiftのSendableプロトコルを使用する際の注意点とその対処法について解説します。

SendableプロトコルはSwiftの同時実行性をサポートするためのものですが、適切に使用しないと思わぬエラーやバグの原因となることもあります。

ここでは、そのような場面を避けるためのヒントとサンプルコードを交えてご紹介します。

○非同時実行性の違反時の挙動と対処法

Sendableプロトコルを適用する際、非同時実行性の違反が発生すると、ランタイムエラーが発生することがあります。

これは、データが同時に複数のスレッドやコンテキストからアクセスされ、データの整合性が保たれない場合に発生するエラーです。

この違反の主な原因は、Sendableが適用されたオブジェクトが複数のスレッドやコンテキストから同時にアクセスされることです。

具体的には、グローバル変数やクラスのスタティックプロパティにSendableを適用し、それを複数の場所から同時に参照・変更するとこのような問題が起きる可能性があります。

対処法としては次の点を注意してプログラミングすることが推奨されます。

  1. Sendableを適用するデータは、可能な限りスコープを限定して使用する。例えば、関数のローカル変数や構造体のプロパティとして利用する。
  2. グローバル変数やクラスのスタティックプロパティにSendableを適用する際は、同時アクセスが発生しないように注意する。
  3. 必要に応じて同期プリミティブ(例:セマフォ、ミューテックス)を使用して、データへのアクセスを制御する。

○サンプルコード6:注意点を回避するためのプラクティス

下記のサンプルコードは、Sendableを適用したデータを複数のスレッドから安全にアクセスする方法を表しています。

// Sendableを適用した構造体の定義
struct UserData: Sendable {
    var name: String
    var age: Int
}

// グローバル変数としてのUserData
var globalUserData = UserData(name: "Taro", age: 30)

// データへのアクセスを制御するためのセマフォ
let semaphore = DispatchSemaphore(value: 1)

// 同時実行のタスク
DispatchQueue.global().async {
    semaphore.wait()  // アクセス開始前にセマフォを取得
    print(globalUserData.name)
    semaphore.signal()  // アクセス終了後にセマフォを解放
}

DispatchQueue.global().async {
    semaphore.wait()
    globalUserData.age += 1
    semaphore.signal()
}

このコードでは、UserDataというSendableを適用した構造体をグローバル変数として定義しています。

そして、このデータへのアクセスを制御するためにDispatchSemaphoreを使用しています。

これにより、データの同時アクセスを防ぎ、データの整合性を保つことができます。

このサンプルコードを実行すると、”Taro”という名前が表示され、globalUserDataageプロパティが1増加します。

このように、同時アクセスの問題を回避するためのプラクティスを実践することで、Sendableプロトコルを安全に活用することが可能となります。

●Sendableのカスタマイズ方法

SwiftのSendableプロトコルは、データを同時実行的なコンテキスト間で安全に転送するための新しい方法を提供します。

しかし、あらゆるデータ型が自動的にこのプロトコルに適合するわけではありません。

独自のデータ型や既存のデータ型に対してSendableを適用したい場合は、カスタマイズが必要となる場面があります。

ここでは、カスタムSendableプロトコルの実装方法や、既存の型にSendableを適用する方法について、サンプルコードを交えて詳しく解説していきます。

○サンプルコード7:カスタムSendableプロトコルの実装

このコードでは、独自のCustomSendableプロトコルを実装し、そのプロトコルに適合する新しいデータ型を作成するコードを表しています。

この例では、CustomDataという型を定義し、それをCustomSendableに適合させています。

// CustomSendableプロトコルの定義
protocol CustomSendable {}

// CustomDataという型の定義
struct CustomData: CustomSendable {
    var value: Int
}

let data = CustomData(value: 10)

このコードを実行すると、CustomData型のdataという名前のインスタンスが作成され、そのvalueプロパティには10という値が設定されます。

○サンプルコード8:既存の型にSendableを適用する

Swiftでは、extensionを使用することで既存の型に新しい機能を追加することができます。

このコードでは、既存のString型にSendableプロトコルを適用するコードを表しています。

この例では、String型を拡張してSendableに適合させ、新しい機能を追加しています。

// String型にSendableプロトコルを適用
extension String: Sendable {}

let message = "Hello, Sendable!"

このコードを実行すると、String型がSendableプロトコルに適合するようになります。

そして、messageという名前の文字列が”Hello, Sendable!”として定義されます。

まとめ

SwiftのSendableプロトコルは、同時実行的なコンテキスト間でデータを安全に転送するための新しい方法を提供しています。

この記事では、Sendableの基本的な役割や使い方、そしてカスタマイズ方法について詳しく解説しました。

また、独自のデータ型を安全に同時実行で使用するためや、既存の型に新しい機能を追加するために、Sendableプロトコルのカスタマイズがいかに有用であるかを理解することができたかと思います。

Swiftの同時実行性を最大限に活用し、安全かつ効率的なアプリケーション開発を進めるために、今後もSendableの知識を深め、適切に活用していきましょう。