CodeZine(コードジン)

特集ページ一覧

Swift 5.5でサポートされた非同期処理の使い方――async/await構文と新UIクラス

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2022/01/28 11:00

 Swiftの基本的な事柄が分かる読者を対象に、Swift 5.5からサポートされた非同期処理について説明します。非同期処理とそれまでの処理の違い、非同期処理の具体的な使い方、簡単なサンプルを作成して処理が短く記述できることを、例を挙げながら解説します。

目次

はじめに

 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を解析するサンプルを再掲載します。サンプルの詳細は上記の記事にて確認できます。

[リスト1]JSONを解析する処理(Sample/JsonViewController.swift抜粋)
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つのプロセスが必要です。

  1. URLSessionクラスで処理を実行するオブジェクトを定義する。
  2. dataTaskメソッドでJSONを解析する処理をクロージャで定義する。
  3. (2)で定義した処理をresume()メソッドで実行する。

 このように処理が長くなりがちです。またボタンを押した時に処理を開始する場合などは、(2)と(3)を記述する場所が離れてしまい、コードの可読性が下がってしまうことも考えられます。

async/await構文を用いた処理

 async/await構文のasyncとawaitにはそれぞれ次の意味があります。

async/await構文の概要
名前 概要 記述する場所
async 非同期で実行したいメソッドを宣言 メソッド名の後方
await 処理が終わるまで待機 待機するメソッド名の前方

 メソッドを宣言する時に、asyncをつけることで非同期に処理を行うことを明確にできます。awaitをつけて作成したメソッドを呼び出すことで、結果が返るまで待機する処理を行うことができます。

 このようにasyncとawaitを組み合わせることで、開発者の意図する非同期の処理を容易に作成/実行することができます。また、既存クラスのメソッドにおいても、awaitを記述することで、処理が終わるのを待って結果を取得することができます。

 前項のJSONを解析する処理を、async/await構文を用いると、次のように表せます。

[リスト2]JSONを解析する処理(JsonSample/PersonLoader.swift抜粋)
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を記述します。

[リスト3]ボタン押下時に非同期処理の呼び出し(JsonSample/ContentView.swift抜粋)
    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の内容をコンソールに出力します。


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

著者プロフィール

  • WINGSプロジェクト 片渕 彼富(カタフチ カノトミ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2022 Shoeisha Co., Ltd. All rights reserved. ver.1.5