😬
[Swift]Async/Awaitが導入された背景
概要
Swift Evolutionを読んで async/await
記法が導入された背景をまとめる。
Async/Await
記法は従来のCompletion Handler
による記法と比較して、以下の点で優れている。
- ネストせずに複雑な処理を記述できる
- コンパイラやツールのサポートを受けることができる
- パフォーマンスが良い
Completion Handler
の問題点
従来の従来の記法では、非同期処理を行うときには、Completion Handler
を用いるのが一般的であった。
// `Completion Handler`の例
func asynFunc(completionBlock: (()->Void) {
asyncOperation {
// 非同期処理が終ってから、`Completion Block`を呼ぶ
completionBlock()
}
}
しかし、以下のような問題点があった。
- ネストが深くなり、可読性が良くない
-
Completion Handler
の呼び忘れを防ぐのが難しい
ネストが深くなり、可読性が良くない
たとえば以下のようにネストが深くなると、デバッグや変更のときに、処理を追うのが難しい。
// ネストが深いときの例
func processImageData1(completionBlock: (_ result: Image) -> Void) {
loadWebResource("dataprofile.txt") { dataResource in // <= nest1
loadWebResource("imagedata.dat") { imageResource in // <= nest2
decodeImage(dataResource, imageResource) { imageTmp in // <= nest3
dewarpAndCleanupImage(imageTmp) { imageResult in // <= nest4
completionBlock(imageResult)
}
}
}
}
}
Completion Handler
の呼び忘れを防ぐのが難しい
どのケースでも必ずCompletion Handler
を呼ぶことを期待していても、ネストが深い場合や条件分岐が複雑な場合に呼び忘れてしまうことがある。
また、Completion Handler
の呼び忘れをCompilerはチェックしてくれない。
// `Completion Handler`を呼び忘れてもエラーにはならない。
func processImageData1(completionBlock: (_ result: Image?) -> Void) {
loadWebResource("dataprofile.txt") { dataResource in // <= nest1
guard hoge else {
return // <- completionBlock を呼ばずにreturn
}
loadWebResource("imagedata.dat") { imageResource in // <= nest2
guard hoge else {
return
completionBlock(nil)
}
decodeImage(dataResource, imageResource) { imageTmp in // <= nest3
guard hoge else {
return // <- completionBlock を呼ばずにreturn
}
dewarpAndCleanupImage(imageTmp) { imageResult in // <= nest4
guard hoge else {
completionBlock(nil)
return
}
completionBlock(imageResult)
}
}
}
}
}
Async/Awaitの利点
利点として以下の3つを挙げることができる
- ネストせずに複雑な処理を記述できる
- コンパイラやツールのサポートを受けることができる
- パフォーマンスが良い
ネストせずに複雑な処理を記述できる
"従来の記法"のサンプルも、async/await
では、ネストなしで記述できる。
これによって複雑な処理も、可読性を損わずに記述できる。
// `Completion Handler`の例を`async/await`で書き変える
func processImageData1() async -> Image {
let dataResource = try await loadWebResource("dataprofile.txt")
let imageResource = try await loadWebResource("imagedata.dat")
let imageTmp = try await decodeImage(dataResource, imageResource)
let imageResult = try await dewarpAndCleanupImage(imageTmp)
return imageResult
}
コンパイラやツールのサポートを受けることができる
-
Completion Block
を使用せずに記述できるため、そもそも呼び忘れが発生しない。 - 関数の戻り値はCompilerがチェックしてくれる。(想定された型をreturnし忘れるとerrorとなる)
パフォーマンスが良い
非同期処理では、途中で処理を中断し、別の処理が行われる。
処理間の移動のときに、処理の文脈(Context)を変える必要がある(Context Switching)。
Async/Await
では、処理を中断するときに、文脈の情報を、"継続(Continuation)オブジェクト"という形で、保存する。
また、処理を再開するときは、"継続オブジェクト"を読み込むことで、中断前の状態を復元できる。
処理の中断のときに、"Thread"の変更は行わないため、Context Switchingのコストが小さい。
Discussion