🙆‍♀️

【SwiftUI】Taskが値を返すタイミングに注意する

に公開

概要

  • 例えばボタンなどを押して発火するTask処理の中で、その一連の中で呼んでいるメソッドの中でもさらにTaskを作成している場合、そのメソッドをawaitしようと思うとできないという問題が発生する
  • Taskは作成されたらすぐに返されるという点の理解が重要

デモ

正常(Task1回)の場合

struct ContentView: View {
    var body: some View {
        VStack {
            Button("実行") {
                log("Task外: ボタンの処理開始")
                Task {
                    log("Task内: asyncMethodを呼び出します")
                    await asyncMethod()
                    log("Task内: asyncMethodが完了しました")
                }
                log("Task外: ボタンの処理終了")
            }
            .padding()
        }
    }
    
    private func asyncMethod() async {
        log("asyncMethodの中身開始")
        try? await Task.sleep(nanoseconds: 2_000_000_000)
        log("asyncMethodの中身終了")
    }
    
    private func log(_ message: String) {
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss"
        print("\(formatter.string(from: Date())) \(message)")
    }
}

出力

11:54:19 Task外: ボタンの処理開始
11:54:19 Task外: ボタンの処理終了
11:54:19 Task内: asyncMethodを呼び出します
11:54:19 asyncMethodの中身開始
11:54:21 asyncMethodの中身終了
11:54:21 Task内: asyncMethodが完了しました
  • ポイントとしては、
    • Buttonに直接渡されているTaskでは、その実行は待たずに Task外: ボタンの処理終了が出力されている
    • asyncMethodの完了を待つためのawaitは想定通りに機能していて、Task内: asyncMethodが完了しましたのログはちゃんと2秒後に出ている

問題となる場合(Taskが入れ子)

  • asyncMedhodの中にさらにTaskを入れる
    var body: some View {
        VStack {
            Button("実行") {
                log("Task外: ボタンの処理開始")
                Task {
                    log("Task内: asyncMethodを呼び出します")
                    await asyncMethod()
                    log("Task内: asyncMethodが完了しました")
                }
                log("Task外: ボタンの処理終了")
            }
            .padding()
        }
    }
    
    private func asyncMethod() async {
        log("asyncMethodの中身開始")
        Task {
            log("asyncMethodのTask内: sleepを開始します")
            try? await Task.sleep(nanoseconds: 2_000_000_000)
            log("asyncMethodのTask内: sleepが完了しました")
        }
        log("asyncMethodの中身終了")
    }

出力

11:57:14 Task外: ボタンの処理開始
11:57:14 Task外: ボタンの処理終了
11:57:14 Task内: asyncMethodを呼び出します
11:57:14 asyncMethodの中身開始
11:57:14 asyncMethodの中身終了
11:57:14 Task内: asyncMethodが完了しました
11:57:14 asyncMethodのTask内: sleepを開始します
11:57:16 asyncMethodのTask内: sleepが完了しました
  • ポイントとしては、
    • asyncMethodの完了が待たれなくなっていて、Task内: asyncMethodが完了しましたが即時に表示されている

Discussion