Swift 5.5には、async/await、actors, throwingプロパティなど、膨大な改良が施されています。あまりにも多くのことが変わっているので、「Swift 5.5で新しくないものは何か」と尋ねるのが初めて簡単になったかもしれません。
この記事では、コードサンプルを使ってそれぞれの変更点を説明し、実際にどのように動作するかを確認していただきます。これは、非常に多くの巨大な Swift Evolution の提案が、これほど緊密にリンクされた初めてのケースです。そのため、これらの変更を首尾一貫した流れの中で整理しようとしましたが、並行処理の作業のいくつかの部分は、いくつかの提案を読んだ後でなければ本当に理解できません。
SE-0298では、新しいAsyncSequenceプロトコルを使って、非同期の値の列をループする機能を導入しています。これは、計算に時間がかかったり、まだ利用できないなどの理由で、すべての値を一度に計算するのではなく、利用できるようになった値を順番に処理したい場合に便利です。
AsyncSequenceの使い方はSequenceとほぼ同じですが、型がAsyncSequenceとAsyncIteratorに準拠していることと、next()メソッドにasyncのマークがついていることが違います。シーケンスの終了時には、sequenceと同様にnext()からnilを返すようにします。
例えば、1から始まり、呼ばれるたびにその数を倍にするDoubleGeneratorシーケンスを作ることができます。
struct DoubleGenerator: AsyncSequence {
typealias Element = Int
struct AsyncIterator: AsyncIteratorProtocol {
var current = 1
mutating func next() async -> Int? {
defer { current &*= 2 }
if current < 0 {
return nil
} else {
return current
}
}
}
func makeAsyncIterator() -> AsyncIterator {
AsyncIterator()
}
}
非同期シーケンスができたら、非同期コンテキストでfor awaitを使って、次のようにその値をループさせることができます。
func printAllDoubles() async {
for await number in DoubleGenerator() {
print(number)
}
}
AsyncSequenceプロトコルでは、map()、compactMap()、allSatisfy()など、さまざまな一般的なメソッドのデフォルト実装も提供しています。例えば、ジェネレータが特定の数値を出力するかどうかを確認するには、次のようにします。
func containsExactNumber() async {
let doubles = DoubleGenerator()
let match = await doubles.contains(16_777_216)
print(match)
}
繰り返しになりますが、これを使用するには、非同期コンテキストである必要があります。