Swiftでasync/awaitを使ってみよう
ついに、Swiftでasync/awaitが使えるようになりました。
処理の流れが追いにくかった非同期処理を簡単に書くことができます!
早速見ていきましょう!
コードを比較してみよう!
今までのコード
struct GitHubClient {
static func fetchData(completion: @escaping (Result<[Repository], APIError>) -> Void) {
var request = URLRequest(url: URL(string: "https://api.github.com/search/repositories?q=q")!)
request.setValue("application/vnd.github.v3+json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { data, response, error in
if error != nil {
completion(.failure(.network))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
completion(.failure(.badResponse))
return
}
if let data = data {
guard let result = try? JSONDecoder().decode(ResponseData.self, from: data) else {
completion(.failure(.decode))
return
}
completion(.success(result.toRepos()))
} else {
completion(.failure(.error))
}
}.resume()
}
}
改めて、処理の順番が複雑ですね。上から下に行って、また上に行って、、、
初めてこの非同期処理を書いた時は本当にどの順番で処理されているのかわからず混乱しましたw
これがネストするのを考えると、、、、、、、、ですね。
さて次はasync/awaitを用いたコードです。
async/awaitを用いたコード
struct GitHubClient {
static func fetchData() async throws -> [Repository] {
var request = URLRequest(url: URL(string: "https://api.github.com/search/repositories?q=q")!)
request.setValue("application/vnd.github.v3+json", forHTTPHeaderField: "Accept")
guard let (data, response) = try? await URLSession.shared.data(for: request) else {
throw APIError.network
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
throw APIError.badResponse
}
guard let result = try? JSONDecoder().decode(ResponseData.self, from: data) else {
throw APIError.decode
}
return result.toRepos()
}
}
なんと言うことでしょう。
処理の流れが一方向になりました!!!(コード見るだけじゃわからないけどw)
ネストも深くなく、短くコードを書くことができましたね。
簡単に解説
まずメソッドにasyncとありますが、これは、このメソッドが非同期で動くメソッドであることを明示しています。
static func fetchData() async throws -> [Repository]
このようにasyncを記述することでメソッド内の非同期の処理が完了するのをawaitを用いて待機させることができるようになります。
待機と言っても、別スレッドで待機させるので、待っている間は他の処理を行うことができます。
非同期の処理が完了し次第、結果を取得できます。
注意点
非同期のメソッド(asyncメソッド)を呼び出す場合、awaitを用いて呼び出す必要があります。
そして、awaitを用いるには呼び出している関数が非同期である必要があるためasyncを用いなければいけません。
func call() async {
do {
let repos = try await GitHubClient.fetchData()
DispatchQueue.main.async {
self.repos = repos
}
} catch(let error) {
print(error)
}
}
そう、つまり、、、asyncがどんどん外に漏れ出していきます。
そしてライフサイクルなどで呼び出した時、メソッドにasyncを付けれなくなり、警告が表示されます。
つまり、asyncは使えない!!!!!!
まあ、嘘です。
WWDCの動画をしっかり見ずに使用したら、僕がそうなったと言うだけですね。はい。
.onAppear {
await repo.call() //警告が表示される
}
SwiftUIのコードを一部取ってきました。
onAppaerの説明はこんな感じで書いてあります。
クロージャの型が、
(() -> Void)?
と決められているため、非同期なクロージャに変更することができません。
ここで、Taskを用いることで同期処理の中で非同期の処理を呼び出すことができます。
.onAppear {
Task {
await repo.call()
}
}
これでasyncが漏れ出していかない!!!!
終わりに
こんな感じでasync/awaitを使用することができます。
大雑把な説明なので正しくしっかり理解したい方はWWDCの動画を視聴することをお勧めします。
今回の説明をもっと詳しく丁寧にしてくれてます。
https://developer.apple.com/videos/play/wwdc2021/10132/
少しでも、初学者の方の助けになれば幸いです。