dely Tech Blog

クラシル・TRILLを運営するdely株式会社の開発ブログです

Xcode13のConcurrency関連の新機能を使ってアプリを作ってみた

iOSエンジニアの石田です。

WWDC2021では、ConcurrrencyにまつわるSwiftの新しい文法やSwiftUIの新しいViewが紹介されました。

本記事では、それらを使ったアプリを作りながら、新機能を紹介したいと思います。

作るアプリは、GitHubのAPIを使って、ユーザの画像をUICollectionViewのような形式で表示するものです。 ユーザ情報は https://api.github.com/users から取得でき、ユーザ画像は avatar_url で定義されています。

f:id:trill_tech:20210810093258p:plain:w300

async/await

まず、API経由でユーザ情報を取得します。

URLSessionを使って非同期通信を行う場合、コールバックでレスポンスを処理していました。 しかし、Swift5.5で追加されたasync/awaitを使うことでコールバックが不要になります。

async/await自体は他の言語でも実装されている文法で、非同期処理をより簡潔に記述することができます。

以下が例となります。 簡単のためにエラーハンドリングなどは全て無視しています。

func fetch() async {
    let users = try! await fetchUsers()
}

func fetchUsers() async throws -> [User] {
    let url = URL(string: "https://api.github.com/users")!
    let (data, _) = try await URLSession.shared.data(from: url)
    let users = try JSONDecoder().decode([User].self, from: data)
    return users
}

関数の返り値として非同期処理の結果を返すことができます。

URLSessionの処理をawaitをつけて呼び出し、関数自体をasyncで定義しています。 asyncをつけた関数を呼ぶ際は、awaitをつけて呼びます。

async/awaitを利用することでコールバックを使うことなく、同期処理と同じ記述で非同期処理を書くことができます。

AsyncImage

もともとSwiftUIにはImageという画像表示のためのViewがありましたが、URLを直接入力することはできず、HTTP通信で画像をダウンロードする場合はひと手間加える必要がありました。

Xcode13で登場したAsyncImageではそういった手間は不要で、URLを入力すると画像が自動でダウンロードされます。

使い方は極めてシンプルで、例えば以下のようになります。

AsyncImage(url: URL(string: "https://example.com/icon.png"))

プレースホルダーを指定し、画像をリサイズする場合は以下のようになります。

AsyncImage(url: url) { image in
    image.resizable()
} placeholder: {
    ProgressView()
}
.frame(width: 100, height: 100)

AsyncImageの引数はURL?型なので、unwrapすることなく気軽に使えるのも良いところだと思います。

async/awaitとAsyncImageを使ったサンプル

上述したasync/awaitとAsyncImage、そしてLazyVGridを使ってGitHubのユーザ画像をUICollectionViewのように2カラムでユーザ画像を表示します。

import SwiftUI

struct User: Decodable, Identifiable {
    let id: Int
    let avatarUrl: String
}

struct ContentView: View {
    @State var users = [User]()

    let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2)

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns) {
                ForEach(users) { user in
                    AsyncImage(url: URL(string: user.avatarUrl)) { image in
                        image
                            .resizable()
                            .scaledToFill()
                    } placeholder: {
                        ProgressView()
                    }
                }
            }
        }
        .task {
            users = try! await fetchUsers()
        }
    }

    private func fetchUsers() async throws -> [User] {
        let url = URL(string: "https://api.github.com/users")!
        let (data, _) = try await URLSession.shared.data(from: url)
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        let users = try decoder.decode([User].self, from: data)
        return users
    }
}

API通信など含めても非常に簡潔に書くことができました。

エラーハンドリングを全て無視しているので実際はこんなに少ない行数にはなりませんが、コールバックがないため見通しが良くなっていると思います。

まとめ

簡単なアプリの実装を通して、Xcode13で追加された機能を紹介しました。

iOS15からの機能なので、iOS14をサポートしている間は使うことができないのですが、使うのが非常に楽しみな機能になっています。

async/awaitの登場によってRxSwiftやCombineがどのように捉えられ、使われていくのかもウォッチしていきたいところです。

TRILLでは、今後さらにサービスを大きくすべくアップデートを進めており、エンジニア、デザイナー、PdMを積極採用中です。
もし興味がありましたら、気軽にアクセスしていただければと思います。

dely.jp