1
1

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】Task-isolated value of type '() async -> Void' passed as a strongly transferred parameter; later accesses could raceの原因と解決法

Posted at

概要

Task-isolated value of type '() async -> Void' passed as a strongly transferred parameter; later accesses could race というエラーが発生して、その原因と解決策を調べたので備忘録として残しておきます。SwiftのConcurrency(非同期処理)に関連したエラーで、タスク分離やSendableプロトコルに関する理解を深めるための例として役立つと思います。

発生した時のコードを簡略化したのものが以下になります。

final class ViewModel {
    func fetchData() async {
        print("Fetching data...")
    }

    func runTask() {
        Task {
            await fetchData()
        }
    }
}

原因

このエラーの原因を一言でいうと、「Sendable じゃない関数が異なるTask間で渡されているから」です。

Sendableとは

Sendable「値が複数のスレッドから安全にアクセスできること」 を保証するプロトコルです。もしSendableに準拠してない値や関数が別々のスレッドから参照されると、データ競合や安全性の欠如につながる可能性があります。そのため異なるTask間で値や関数を参照する際には、Sendableに準拠していなければいけないというルールがあります。

エラーの詳細

上記のコードでは、runTaskでTaskを新しく作ってfetchDataを呼び出してますが、fetchDataはSendableではないのでこのエラーが発生していることになります。

  1. runTaskメソッドで新しいTaskを作成
  2. 新しいTask内で、fetchDataメソッドを呼び出す
  3. fetchDataがSendableに準拠していないため、「タスク間での安全な転送が保証されない」としてエラーが発生する

簡単に図にするとのこんな感じです。

解決策

このエラーを解決するためのアプローチを3つ紹介します。それぞれの方法にはメリットがあり、実際の要件やコード状況に応じて選択してください。

クラスにSendableを準拠させる

1つ目の解決策は、クラスにSendableを準拠させるものです。

final class ViewModel: Sendable {
    func fetchData() async {
        print("Fetching data...")
    }

    func runTask() {
        Task { [weak self] in
            guard let self = self else { return }
            await self.fetchData()
        }
    }
}

ポイント

  • クラスがSendableに準拠させると、そのインスタンスのメソッドがスレッドセーフであることが保証されるのでエラーが解消される

新しいTaskを作らない

2つ目の解決策は、そもそも新しいTaskを生成しないことです。

final class ViewModel {
    func fetchData() async {
        print("Fetching data...")
    }

    func runTask() async {
        await self.fetchData()
    }
}

ポイント

  • runTask自体を非同期メソッドに変更
  • 新しいTaskを生成しないため、タスク間での安全性の問題がそもそも発生しない
  • シンプルな解決策

参照するメソッドをstaticにする

3つ目の解決策は、参照するメソッドをstaticにすることです。

final class ViewModel {
    static func fetchData() async {
        print("Fetching data...")
    }

    func runTask() {
        Task {
            await ViewModel.fetchData()
        }
    }
}

ポイント

  • staticメソッドはインスタンスの状態に依存しないため、スレッドセーフに扱える

参考にした記事

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?