はじめに
Swift 5.5からサポートされた非同期処理の具体的な使い方について説明します。
対象読者をSwiftの基本的な使い方が分かる方としている関係上、SwiftおよびSwiftUI自体の仕様やXcodeの使い方については解説を割愛する場合があることをご了承ください。
対象読者
本記事は、次の方を対象にしています。
- Swiftの基本的なプログラムができる方
- Xcodeを使える方
async/await構文を使った処理
非同期処理を利用するSwift言語の構文として、async/await構文が定義されました。async/await構文を利用すると、デリゲートやクロージャを経由せずに処理の結果を取得する直感的な記述ができます。
async/await構文以前の処理
async/await構文以前のSwiftの基本的な処理の例として、以下の記事のJSON解析の処理が挙げられます。
この記事にある、RANDOM USER GENERATOR APIからの戻りJSONを解析するサンプルを再掲載します。サンプルの詳細は上記の記事にて確認できます。
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// RANDOM USER GENERATORのAPIにアクセスし、APIの結果をJSONで取得
let str : String = "https://randomuser.me/api"
let url : URL = URL(string: str)!
// URLRequestを生成してJSONのデータを取得
let request: URLRequest = URLRequest(url:url)
let session = URLSession.shared // ----------(1)
let task : URLSessionDataTask = session.dataTask(with: request, completionHandler: {(data, response, error) in // ----------(2)
// エラーがあれば表示
if(error != nil) {
print(error ?? "")
return
}
// APIからの戻り値がなければ処理を終了
guard let responseData = data else{ return }
do {
// JSONDecoderクラスのインスタンスを生成
let decoder = JSONDecoder()
// JSONを解析して作成した構造体の通りにマッピング
let resultList = try decoder.decode(ResultListModel.self, from: responseData)
print(resultList)
// JSONを解析した後、Person構造体にマッピングされたデータを取り出す
for person in resultList.results {
print(person.gender)
print(person.name.title)
print(person.location.state)
print(person.email)
}
} catch {
print("JSONの解析でエラーが起きました")
}
})
task.resume() // ----------(3)
}
サンプルを確認すると、JSONを解析する処理は次の3つのプロセスが必要です。
- URLSessionクラスで処理を実行するオブジェクトを定義する。
- dataTaskメソッドでJSONを解析する処理をクロージャで定義する。
- (2)で定義した処理をresume()メソッドで実行する。
このように処理が長くなりがちです。またボタンを押した時に処理を開始する場合などは、(2)と(3)を記述する場所が離れてしまい、コードの可読性が下がってしまうことも考えられます。
async/await構文を用いた処理
async/await構文のasyncとawaitにはそれぞれ次の意味があります。
| 名前 | 概要 | 記述する場所 |
|---|---|---|
| async | 非同期で実行したいメソッドを宣言 | メソッド名の後方 |
| await | 処理が終わるまで待機 | 待機するメソッド名の前方 |
メソッドを宣言する時に、asyncをつけることで非同期に処理を行うことを明確にできます。awaitをつけて作成したメソッドを呼び出すことで、結果が返るまで待機する処理を行うことができます。
このようにasyncとawaitを組み合わせることで、開発者の意図する非同期の処理を容易に作成/実行することができます。また、既存クラスのメソッドにおいても、awaitを記述することで、処理が終わるのを待って結果を取得することができます。
前項のJSONを解析する処理を、async/await構文を用いると、次のように表せます。
class PersonLoader {
func asyncLoad() async throws { // ----------(1)
let url = URL(string:"https://randomuser.me/api")!
let (data, response) = try await URLSession.shared.data(from: url) // ----------(2)
// レスポンスコードが200でなければエラーを返す
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw PersonError.serverError }
// JSONを解析して作成した構造体の通りにマッピング
// データがなければエラーを返す
guard let decoded = try? JSONDecoder().decode(ResultListModel.self, from: data) else { throw PersonError.noData }
// JSONを解析した後、Person構造体にマッピングされたデータを取り出す
for person in decoded.results {
print(person.gender)
print(person.name.title)
print(person.location.state)
print(person.email)
}
}
}
処理全体はPersonLoaderクラスとして宣言しています。クラス内でJSONを取得してパースする処理を、asyncLoadのメソッド名で定義します。非同期に実行したいので、メソッド名の後ろに「async」を記述します(1)。
JSONの取得時と解析時にはエラーが起きるかもしれないので、「throws」をつけてエラーを返せるようにしています。
URLからデータを取得する処理は、URLSessionのdataメソッドを利用しています。この時に、データを取得する処理を待機させたいので、dataメソッドの前に「await」を記述しています(2)。awaitを記述することで、メソッドの戻り値が得られるまで処理が待機できます。(2)の時点で戻り値が得られるため、値を得るためにデリゲートやクロージャを記述する必要はなくなります。その後の処理に関しては、前項のサンプルと変わりません。
作成したPersonLoaderクラスのasyncLoadメソッドを、ボタンを押した時に呼び出すには、次のようにasyncLoadメソッドの前にawaitを記述します。
let loader = PersonLoader()
var body: some View {
Button(action: {
do {
try await loader.asyncLoad()
} catch {
print(error)
}
}) {
Text("Button")
}
}
asyncLoadメソッドの前にawaitを記述して、処理が終わるのを待つようにしています。asyncLoadメソッド自体がエラーを返すので、do-catch文とtryも忘れずに記述します。
サンプルを実行すると、前項のサンプルの実行結果と同じようにAPIから取得したJSONの内容をコンソールに出力します。
