はじめに
今回はasync/awaitの基本的な使い方について備忘録てきに記事にしてみます。
Swift Concurrencyって?
Swift ConcurrencyはiOS15から導入されました。async/awaitもSwiftConcurrencyの一部です。
async/awaitの他にもデータ競合問題を解消するためのActorなども導入されました。
async/awaitのいいところ
「非同期処理を同期的に書ける」
この点について紹介できたらなと思っています。
今回紹介するものがasync/awaitの全てではないので、気になったらぜひ調べてみてください。
いつ使うの?
今回はこんなシチュエーションで考えてみます。
想定シチュエーション
- サーバーにリクエストをする
- そのリクエストが返ってきた後に、また別のリクエストをする
Closureを使った実装方法
サーバーとのやりとりをまとめたClass
- おそらく馴染みは深いと思うので、サクッとコードだけ載せておきます。
class ServerTaskWithoutAsyncAwait {
// レスポンスが帰ってきた後に、別のリクエストを呼ぶとここが非常に読みにくくなる。(ネスト深い)
func executeBothTask(completion: @escaping (Int) -> Void) {
task1 { [weak self] num1 in
self?.task2 { num2 in
completion(num1 + num2)
}
}
}
private func task1(completion: @escaping (Int) -> Void) {
sleep(1)
print("タスク1完了")
completion(2)
}
private func task2(completion: @escaping (Int) -> Void) {
sleep(2)
print("タスク2完了")
completion(3)
}
}
実行処理
@IBAction private func tappedWithoutAsyncAwaitButton(_ sender: UIButton) {
print("closure")
serverTaskWithoutAsyncAwait.executeBothTask { total in
print("total: \(total)")
}
}
実行結果
タスク1完了 // 実行の1秒後に表示
タスク2完了 // さらに2秒後に表示
total: 5
async/awaitを使った方法
とりあえず、使い方をざっと記します。
非同期の処理を含む関数
- 非同期の処理を行う関数にはasyncキーワードを付与します。
- 戻り値も普通にかけます。(普通にという表現が気持ち悪いですが、まぁ伝わるでしょう。)
private func task1() async -> Int {
sleep(1)
print("タスク1完了")
return 3
}
private func task2() async -> Int {
sleep(2)
print("タスク2完了")
return 1
}
asyncをつけた関数の呼び出し
- asyncをつけた関数を呼び出すには、awaitをつけます。
- そして、awaitを使ってる関数には、asyncを付与する必要があります
- 今回の例で言うと
executeBothTask()
にもawaitをつける必要があります。
func executeBothTask() async -> Int {
let num1 = await task1()
let num2 = await task2()
return num1 + num2
}
Class全体はこんな感じ
class ServerTaskWithAsyncAwait {
private func task1() async -> Int {
sleep(1)
print("タスク1完了")
return 3
}
private func task2() async -> Int {
sleep(2)
print("タスク2完了")
return 1
}
func executeBothTask() async -> Int {
let num1 = await task1()
let num2 = await task2()
return num1 + num2
}
}
呼び出し
- ボタンタップをしたときに呼び出すことを想定します
-
executeBothTask()
にはasyncが付与されているので、tappedWithAsyncAwaitButton
にもasync
をつけろと言われます - ただ、言われた通り
async
をつけるとそれもダメといわれます。 - その時は、以下のように
Task
で囲ってあげることで正常に動いてくれます
@IBAction private func tappedWithAsyncAwaitButton(_ sender: UIButton) {
print("async/await")
Task {
let total = await serverTaskWithAsyncAwait.executeBothTask()
print("total: \(total)")
}
}
実行結果
async/await
タスク1完了 // 実行1秒後に表示
タスク2完了 // さらに2秒後に表示
total: 4
closureとasync/awaitの比較
以下の2つのコードを見比べてもらうとわかると思いますが、async/awaitを使った方が遥かにすっきりしているように見えるでしょう。
awaitを使うとその処理が終わるまで、進まずその行で待っていてくれます。処理が進まずそこで待っていてくれる。
ということこそが非同期処理を同期的に書けているということです。
同期的に書くことができるので、ネストが深くならずに書けるんですね。
closure
func executeBothTask(completion: @escaping (Int) -> Void) {
task1 { [weak self] num1 in
self?.task2 { num2 in
completion(num1 + num2)
}
}
}
async/await
func executeBothTask() async -> Int {
let num1 = await task1()
let num2 = await task2()
return num1 + num2
}
おわりに
かなり雑に紹介しましたが、どうだったでしょうか?
Swift Concurrencyももっと勉強しまーす。
今回のサンプルはgithubにあるのでよければどうぞ!
ではでは!