🙆♀️
【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秒後に出ている
- Buttonに直接渡されているTaskでは、その実行は待たずに
問題となる場合(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が完了しました
が即時に表示されている
- asyncMethodの完了が待たれなくなっていて、
Discussion