0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【物語で学ぶ Swift Concurrency】データ競合を回避するための安全なデータコピー戦略

Posted at

Swift 6 の並行処理を使っていると、次のようなエラーに出くわすことがあります。

Sending 'result' risks causing data races

この記事では、このエラーがなぜ発生するのか、そしてどのように改善すれば安全に非同期処理でデータを渡せるのかを、先生と太郎の対話形式のお話で解説します。


はじめに

並行処理では、複数のタスクが同じデータにアクセスすると、まるで大切なノートをみんなで同じ原本に書き込むような状態になって、内容がぐちゃぐちゃになってしまいます。Swift 6 では、そのリスクを防ぐため、非同期に値を渡すとき、その値が「Sendable」かどうかを厳しくチェックします。

ここでは、例えば [String: Any] 型の辞書(大切なノート)をそのまま渡すと危険な理由と、安全に渡すためのコピー戦略を見ていきます。


シーン 1:エラーになるコード

登場人物

  • 先生:Swift の並行処理と安全性に詳しい
  • 太郎:Swift を学ぶ高校生

お話

太郎:「先生、こんなコードを書いてみたんですが…」

import Foundation

// 疑似的に非同期で辞書データを取得する関数
func fetchData(completion: @escaping ([String: Any]) -> Void) {
    DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
        let data: [String: Any] = ["name": "Alice", "age": 17]
        completion(data)
    }
}

// fetchData で取得した result をそのまま返す関数
@discardableResult
func getUnsafeData() async throws -> [String: Any] {
    try await withCheckedThrowingContinuation { continuation in
        fetchData { result in
            // このまま result を渡すと、データ競合の可能性があるためエラーになる
            continuation.resume(returning: result)
        }
    }
}

先生:「太郎、このコードでは fetchData で得た result をそのまま非同期に渡しているね。Swift 6 の並行性チェックは、[String: Any] のような汎用的な辞書は、内部に変更可能なデータが含まれている可能性があると判断するんだ。もし複数のタスクが同じデータにアクセスすると、どこかで値が変わってデータ競合(データレース)が起こるリスクがあるため、エラーになってしまうんだ。」

太郎:「つまり、同じノートをみんなで使うと、誰かが勝手に書き換えて内容がぐちゃぐちゃになっちゃう、ということですね。」


シーン 2:改善策 1 – JSON を使ってディープコピーする

先生:「まずは原本のコピーを作る方法だ。これは、原本のノートの写真を撮って新しいコピーを作るようなもの。そうすれば、みんながそのコピーを使っても、元のノートは安全に守られるんだ。」

import Foundation

// 辞書をディープコピーする関数(ノートの写真を撮って新しいノートを作る)
func deepCopy(dictionary: [String: Any]) throws -> [String: Any] {
    // 1. 原本の辞書を JSON データに変換(写真を撮る)
    let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
    // 2. JSON データから新しい辞書(コピー)を作る
    guard let copy = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
        throw NSError(domain: "DeepCopyError", code: 1, userInfo: nil)
    }
    return copy
}

@discardableResult
func getSafeData() async throws -> [String: Any] {
    try await withCheckedThrowingContinuation { continuation in
        fetchData { result in
            do {
                // 原本の辞書をディープコピーして安全なコピーを作成
                let safeResult = try deepCopy(dictionary: result)
                continuation.resume(returning: safeResult)
            } catch {
                continuation.resume(throwing: error)
            }
        }
    }
}

太郎:「この方法なら、原本のノートから写真を撮って新しいコピーを作るので、みんながそのコピーに書き込んでも原本は変わらないんですね!」


シーン 3:改善策 2 – 具体的な型に変換して安全に渡す

先生:「もう一つの方法は、最初から具体的な型、例えば構造体を使ってデータを表すことだ。構造体は値渡しになるので、変数に代入すると自動的にコピーが作られる。これは、原本のノートを最初から封筒に入れて運ぶようなものだよ。」

// 生徒情報を表す構造体(封筒に入れたノートのイメージ)
struct StudentInfo: Sendable {
    let name: String
    let age: Int
}

@discardableResult
func getStudentInfo() async throws -> StudentInfo {
    try await withCheckedThrowingContinuation { continuation in
        fetchData { result in
            if let name = result["name"] as? String,
               let age = result["age"] as? Int {
                // 具体的な型のインスタンスを生成して返す
                let info = StudentInfo(name: name, age: age)
                continuation.resume(returning: info)
            } else {
                continuation.resume(throwing: NSError(domain: "ParseError", code: 0, userInfo: nil))
            }
        }
    }
}

太郎:「この方法なら、最初から生徒情報という具体的な型を使うので、自動的にコピーが作られ、安全に非同期で渡すことができるんですね!」


まとめ

  • エラーの原因

    • [String: Any] のような汎用型は、内部に変更可能なデータを含む可能性があり、そのまま非同期に渡すとデータ競合のリスクがある。
  • 改善策 1:JSON を使ったディープコピー

    • 原本の辞書を一度 JSON データに変換し、そのデータから新しい辞書を作成することで、安全なコピーを作る。
  • 改善策 2:具体的な型に変換する

    • 最初から構造体など具体的な型を使えば、値渡しにより自動的にコピーが作成されるので、非同期処理でも安全にデータを渡せる。

Swift の並行処理では、「原本そのまま渡すと危ない!」という考え方を理解し、適切なコピー戦略を採用することが重要です。皆さんも、プロジェクトでデータの安全性を守るために、ぜひこのコピー戦略を活用してみてください!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?