Swiftの簡単な並列処理サンプル

iPhone などの iOSアプリで並列処理を行う、簡単なサンプルコードを紹介する。

筆者のマシン
Mac mac mini (M1, 2020)
OS Monterey バージョン 12.4
XCode バージョン 13.4.1
Swift バージョン 5.6.1


ここでは次の手順で説明する。

  1. はじめに
  2. 並列に実施する処理として複数のTaskを作成する
  3. 各Taskを実行する

はじめに

iOSアプリにおいて、複数の処理を並列に実行させたいときがある。例えば

  • タスクA
    • あるURLへアクセスし、データを取得する。
    • 取得したデータを用いた表テーブルを画面に表示。
  • タスクB
    • 別のあるURLへアクセスし、データを取得する。
    • 取得したデータを元に作成した文字列を画面に表示。

といったタスクがあるとする。
タスクAの処理が完了した後にタスクBを処理させるのではなく、タスクAとタスクBを並行に処理させたいことがある。

タスクAとタスクBのどちらが先に完了しても問題がない場合は、並列処理をすることで、速やかな画面描画を実現することができる。
ここでは、タスクAとタスクBといった複数タスクを並列処理させる、簡単なサンプルプログラムを紹介する。

尚、本記事で紹介するコード全体(XCodeプロジェクト)は、GitHub で公開している。
(サンプルで用いる音源データはアップしていないため、コード実行時は別途ご準備ください)

並列に実施する処理として複数のTaskを作成する

ここでは、並列に実施する処理として、以下の2つのTaskを作成する。
今回は処理を簡単にするため、非同期処理としてよく扱われるURLへのリクエストとレスポンス受信といった処理ではなく、2秒待つといった処理としている。

  • Task1 : 2秒待って、ワンワンと鳴く。更に2秒待って、ワンワンと鳴く。
  • Task2 : 2秒待って、ニャンと鳴く。

Task1とTask2を同時に実行開始することで、2秒後にワンワンとニャンが同時に鳴き、更にその2秒後にワンワンだけが鳴くようになる。

まず、下記メソッドは2秒待つという処理である。

// This task is a pseudo asynchronous processing and actually sleeps for 2 seconds.
private func taskSleep(taskName: String) async throws {
    print("\(date) \(taskName) Start")
    try await Task.sleep(nanoseconds: 2_000_000_000)
    print("\(date) \(taskName) End")
}

このサンプルでは、引数taskNameでタスク名を受け取り、2秒を待つ前後にログを出力している。

Task1は次の通りである。

// Dog Task
// after await task, a dog barks, again after await task, a dog barks.
Task {
    try await taskSleep(taskName: "Dog-1")
    animalSoundPlayer.playDog()
    try await taskSleep(taskName: "Dog-2")
    animalSoundPlayer.playDog()
}

4行目のtaskSleepメソッドでは、2秒待つと言う処理がなされる。
5行目のanimalSoundPlayer.playDog()は、ワンワンと鳴く自作コードである。
6行目のtaskSleepメソッドでは、更に2秒待つと言う処理がなされる。
7行目のanimalSoundPlayer.playDog()を呼び出すことで、再度ワンワンと鳴く。

Task2は次の通りである。

// Cat Task
// after await task, a cat mews
Task {
    try await taskSleep(taskName: "Cat-1")
    animalSoundPlayer.playCat()
}

4行目のtaskSleepメソッドでは、2秒待つと言う処理がなされる。
5行目のanimalSoundPlayer.playCat()は、ニャンと鳴く自作コードである。

各Taskを実行する

今回のサンプルコードでは、上記の各Taskを iPhone画面上のボタン「Animal Sound Play」をタップした際に動作させる。
ContentView.swiftの全体コードは以下である。

struct ContentView: View {

    private let animalSoundPlayer = AnimalSoundsPlayer()

    // Current date
    private var date: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
        return dateFormatter.string(from: Date())
    }

    var body: some View {
        Button("Animal Sound Play", action: {
            // Dog Task and Cat Task are parallel processes.

            // Dog Task
            // after await task, a dog barks, again after await task, a dog barks.
            Task {
                try await taskSleep(taskName: "Dog-1")
                animalSoundPlayer.playDog()
                try await taskSleep(taskName: "Dog-2")
                animalSoundPlayer.playDog()
            }
            // Cat Task
            // after await task, a cat mews
            Task {
                try await taskSleep(taskName: "Cat-1")
                animalSoundPlayer.playCat()
            }
        })
    }

    // This task is a pseudo asynchronous processing and actually sleeps for 2 seconds.
    private func taskSleep(taskName: String) async throws {
        print("\(date) \(taskName) Start")
        try await Task.sleep(nanoseconds: 2_000_000_000)
        print("\(date) \(taskName) End")
    }
}

上記ContentView.swiftコードを用いたアプリは、次のような画面となる。

画面上の「Animal Sound Play」をタップすることで、Task1とTask2が同時に開始される。

XCode の Simulator でアプリを起動し、画面をタップした時のログは以下の通りである。


2022/08/05 10:06:48 Dog-1 Start
2022/08/05 10:06:48 Cat-1 Start
2022/08/05 10:06:50 Cat-1 End
2022/08/05 10:06:50 Dog-1 End
2022/08/05 10:06:50 Dog-2 Start
2022/08/05 10:06:52 Dog-2 End


10:06:48 に、Task1のtaskSleepメソッド(taskNameはDog-1) と Task2のtaskSleepメソッド(taskNameはCat-1)が実行されており、
いずれも2秒後の10:06:50にメソッド処理が完了する。
この後ワンワンとニャンが同時に鳴く。
その後に、Task1のtaskSleepメソッド(taskNameはDog-2)が開始され、2秒後の10:06:52にメソッド処理が完了した後、ワンワンと鳴く。

つまり、2秒後にワンワンとニャンが同時に鳴き、更にその2秒後にニャンだけが鳴く。
このようにして、複数Taskを並列に実行することができる。

以上

シェアする

  • このエントリーをはてなブックマークに追加

フォローする