【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

を使うと良い。




【Swift】SystemSoundID 一覧がないのですが

ちょっとしたときに、

あらかじめ用意されているシステム音を使いたい。


AudioServicesPlaySystemSound(1000)

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

これだけで音が鳴る。

「1000」 は SystemSoundID というもののようなので、

「どんな音が鳴るのかリスト」を探すが、

公式で見つけられなかった。

以下にそれらしきもの。


👉 AudioServices - iPhone Development Wiki hatena-bookmark

結構、古そうなので今どきの状態を確認したい。

OSやバージョンなど環境によっても違うっぽい。

なので、コードからのぞいてみる。

 

🔊 欠番ID

ID は 2000 ぐらいまでの整数なのですが、

非公式のリストを見る限り、番号が歯抜け状態。

音声データの長さで「欠番らしき」を見分ける。

実際に再生してその時間を取得しました。


let start = Date()
AudioServicesPlaySystemSoundWithCompletion(1000) {
   let elapsed = Date().timeIntervalSince(start)
   print(elapsed)
}

// 1.566209077835083

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

おおまかに 再生時間 50ミリ秒 以下が欠番っぽい。

 

🔊 グリッドでボタン

ボタンを並べて Preview や Simulator や 実機 でどんな音があるのか確かめます 。



 

極力、deprecated な関数は避けていきたいです。

 

🔊 まとめ

実際に音声ファイルを作成する前に、雰囲気を確認したいときに使いたくなります。

しかし、ID が分からない。

そんな人用。

ファイルで探すなら、各環境内でディレクトリ名で走査するといいのですね !


Library/Audio/UISounds
❯ tree .
.
├── 3rd_party_critical.caf
├── AuthenticationMatch_Full.caf
├── AuthenticationMatch_Short.caf
...
├── ussd.caf
├── warsaw.caf
└── wheels_of_time.caf

4 directories, 321 files

あと、アプリとかショートカットもありました。

👉 「Play System Sounds」をApp Storeで hatena-bookmark
👉 System Sounds - Shortcuts hatena-bookmark


🚀 iOS version Market Share

iOS は Android OS に比べて、ユーザーの最新バージョンへの移行が早い。



また、来月早々には更新したいと思います。

👉 Android Version Market Share Worldwide hatena-bookmark