【Swift】非同期関数の直列実行 - withCheckedContinuation

ビルトイン非同期関数の直列処理って自力でやるにはつらいですよね。

まず、ボタンを押して、音を鳴らします。


Button("Play1") {
  AudioServicesPlaySystemSound(1000)
}

純粋なUIスレッドではないところで再生処理がされています。

いわゆる

「UIスレッドと非同期された処理」

ですね。

続いて、


Button("Play2") {
  AudioServicesPlaySystemSound(SystemSoundID(1000))
}

これも同じ音が再生されます。

この組み込みシステム音声を鳴らす関数


func AudioServicesPlaySystemSound(_ inSystemSoundID: SystemSoundID)

👉 AudioServicesPlaySystemSound(_:) | Apple Developer Documentation hatena-bookmark

の引数の型は SystemSoundID ですので直しておきます。

中を見てみると、


/**
    @typedef        SystemSoundID
    @abstract       SystemSoundIDs are created by the System Sound client application
                    for playback of a provided AudioFile.
*/
public typealias SystemSoundID = UInt32

UInt32 の typealias であることが分かります。

この SystemSouldID は他にもたくさんいろいろあります。

が、端末やOSバージョンによって異なるので注意が必要なようですね。


続いて、11個連続再生してみます。


Button("Play3") {
  for i in 1000...1010 {
    AudioServicesPlaySystemSound(SystemSoundID(i))
  }
}

あれ。

残念なことに音が重なって同時に再生されます。

それぞれ、どんな音が鳴っているか分かりません。

非同期処理の関数が並列で処理されているのです。

 

🤔 非同期関数の直列化

連続で順番に音を再生したい。

言い換えれば、

非同期関数の直列処理

をしたいですよね。

まず、音声再生処理終了のタイミングから処理を実行できる

👉 AudioServicesPlaySystemSoundWithCompletion(_:_:) | Apple Developer Documentation hatena-bookmark

を使って音を1つだけ鳴らしてみます。


Button("Play4") {
  AudioServicesPlaySystemSoundWithCompletion(SystemSoundID(1000)) {
    print("completed.")
  }
}

これで、終了のタイミングを取得できます。

続いて、これを連続で実行します。

今どきの Swift では、以下の関数を使うらしいです。

👉 withCheckedContinuation(function:_:) | Apple Developer Documentation hatena-bookmark
👉 withCheckedThrowingContinuation(function:_:) | Apple Developer Documentation hatena-bookmark

これを使って書き換えます。


Button("Play5") {
  Task {
    for i in 1000...1010 {
      await withCheckedContinuation { continuation in
        AudioServicesPlaySystemSoundWithCompletion(SystemSoundID(i)) {
          continuation.resume()
        }
      }
    }
  }
}

クロージャー内は同期処理となって await が必要となりますので Task をかぶせてます。

音声なので、特にスレッドをメインに指定することなどは不要のようです。

以下、それぞれの実行音声動画です。



 

🤔 まとめ

「非同期処理の直列化」は用意されている関数の

👉 withCheckedContinuation(function:_:) | Apple Developer Documentation hatena-bookmark
👉 withCheckedThrowingContinuation(function:_:) | Apple Developer Documentation hatena-bookmark

を使うと良い。