【独学】はじめてのiOSプログラミング – 10.2 エラー処理の追加

エラー処理の追加

前章で作成した Pixabay API から取得した素材検索結果のJSONをパースするクラスを、サンプルアプリで利用できるようにカスタマイズします。

エラーの対応を考える

8.2 UI以外の処理と例外処理の対応 で触れた通り、 Web API を利用するときには2つのエラーが考えられます。
 ・サーバーからJSONの戻りがないエラー  → サーバーで発生したエラー
 ・JSONがパースできないエラー      → データがないエラー
この2つのエラーに対応してみます。

エラーを列挙型で定義する

Pixabay API を利用した時には、前項の2つのエラーが考えられます。Pixabay API 利用時のエラーとして2つをまとめて管理するのが便利です。Swiftには関係する値をまとめてグループ化できる enum という型があります。enum の書式は次のとおりです。

概要 {
  case 詳細1
  case 詳細2
  ・・・
}

概要という大きな枠の中で並列関係にある詳細を case 文で定義します。各詳細は「概要.詳細」の書式でアクセスできます。
上の書式にしたがって Pixabay API 利用時のエラーを定義すると次のようになります。

enum PixabayAPIError: Error {
    case serverError       // サーバーで発生したエラー
    case noData              // データがないエラー
}

サーバーで発生したエラーとデータがないエラーを PixabayAPIError の名前で列挙型で定義しています。プログラムでエラーであることをわかるようにするために、Error プロトコルを継承しています。
https://developer.apple.com/documentation/swift/error
Error プロトコルを利用することで、do – catch 文と try を利用したエラー処理が行えるようになります。
Errorプロトコルのドキュメントに enum でエラーを定義する例が紹介されていますので、余裕があれば目を通しておいてください。
作成した PixabayAPIError の定義は次のように Models.swift に追記してください。

import Foundation

// JSON全体を格納する構造体
struct SearchImageDataModel:Codable{
    let total: Int
    let totalHits: Int
    let hits : [ImageData]
}

// 素材情報を格納す構造体
struct ImageData: Identifiable, Codable  {
    let id: Int
    let largeImageURL: String
}

enum PixabayAPIError: Error {
    case serverError       // サーバーで発生したエラー
    case noData              // データがないエラー
}

エラー時に処理を中断する

Swift では、ある条件下で処理を中断する guard 文があります。guard 文の書式は次のとおりです。

guard  条件 else { return もしくは throw エラー }

guard 文での条件は、値が存在するかやエラーがないかなどが多く使われます。条件に合わない場合には else ブロックの処理が行われます。else ブロックでは処理を終了する return や エラーを返して終了する throw が使われます。
8.1 Web APIを利用する で 紹介した if let 文では、値が存在するという条件に合う場合に if ブロックが実行されるのに対して、guard 文では条件に合わない場合はそこで処理が中断されるという違いがあります。
ImageLoaderクラスのJSONをパースする部分は、 guard 文では次のように書くことができます。

// JSONを解析して作成した構造体の通りにマッピング
guard let decoded = try? JSONDecoder().decode(SearchImageDataModel.self, from: data) else { throw PixabayAPIError.noData }

guard 以下にJSONをパースする処理を記述します。パースできなかった場合は、else ブロックの処理が実行され、ここで前項で定義した PixabayAPIError のデータがないエラーを throw で返します。
guard 文を利用することでJSONがパースできなかった場合に、定義したエラーを返すという処理を実装できます。

型変換とステータスコード

8.3 同期処理と非同期処 で Web API から HTTP通信を行なってデータを取得する URLSession構造体の data メソッドの戻り値は、DataオブジェクトとURLResponseオブジェクトであることを説明しました。
https://developer.apple.com/documentation/foundation/urlsession/3767352-data

 let (data, response) = try await URLSession.shared.data(from: url)

URLResponse オブジェクトは、HTTP通信も含めたURLからのレスポンスを表す汎用的なURLResponse クラスで管理されています。HTTP通信のみのレスポンスは、URLResponse クラスのサブクラスである HTTPURLResponse クラスで管理されます。
https://developer.apple.com/documentation/foundation/httpurlresponse
このようにクラスの継承関係がある場合は、オブジェクト間の変換を容易に行うことができます。

HTTPURLResponse クラスでは、statusCodeプロパティでHTTP通信のステータスコードを参照することができます。HTTP通信のステータスコードには次のものがあります。

ステータスコード概要
200正常
500サーバー内部エラー
401認証エラー
403参照エラー
404存在エラー

statusCodeプロパティの値を参照することで、Web APIから値を取得する処理が正常かどうか判断できます。
プログラムで作成したい処理をまとめると次のようになります。

URLResponseオブジェクト → HTTPURLResponseオプジェクトに変換 → statusCodeプロパティを参照 → Web APIから値を取得する処理が正常かどうか判断

URLResponseオブジェクトからHTTPURLResponseオプジェクトに変換するように、オブジェクトを現在とは異なる型に変換することを型変換といいます。型変換は as 文を利用して次のように行います。

if let 変換後のオブジェクト = 変換前のオブジェクト as? 型 {
    // 変換後のオブジェクトに対する処理
}

型変換に失敗することもあるので、as にオプショナルの「?」をつけて if let 文で処理を記述することが多いです。
ここでは guard 文を利用して、 PixabayAPIError のサーバーで発生したエラーを返す処理を作成します。

// ステータスコードが200でなければエラー
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw PixabayAPIError.serverError }

「( )」で as 文を囲むことで変換後のオブジェクトを取得できます。このオブジェクト自体がオプショナルなので、「?」をつけて statusCodeプロパティでステータスコードを参照しています。
ステータスコードが200の場合を guard 文の条件にしています。ステータスコードが 200 以外の場合に else ブロックが実行され、PixabayAPIError.serverError を返して処理を中断します。

メソッド自体をエラーに対応する

前項まででエラー処理に対応したので、searchImagesメソッド自体をエラーに対応したものに書き換えます。 searchImagesメソッドの中でおこなっていた do – catch 文を廃止して メソッド自体が エラーを返すように throws を追記します。

func searchImages(keyword: String) async throws {
    self.imageDatas = []
    // 処理
}

searchImages メソッド自体に throws をつけてエラー処理に対応することで、searchImages メソッドの中で do – catch 文を記述する必要がなくなります。

クラス全体のコードを確認

最後にエラーに対応した ImageLoader クラスのコードを確認します。今回作成した処理は赤のアンダーラインの部分です。

import Foundation

class ImageLoader {
    
    // Pixabay API key
    let api_key = "xxxxxxxxxxxxxx"
    
    // 素材情報
    var imageDatas: [ImageData] = []
    
    init() {
        self.imageDatas = []
    }
    
    func searchImages(keyword: String) async throws {
        self.imageDatas = []
        
        let urlStr = "https://pixabay.com/api/?key=\(self.api_key)&q=\(keyword)"
        let url = URL(string: urlStr.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)!)!
        
        let (data, response) = try await URLSession.shared.data(from: url)
        
        // ステータスコードが200でなければエラー
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw PixabayAPIError.serverError }
        
        // JSONを解析して作成した構造体の通りにマッピング
        guard let decoded = try? JSONDecoder().decode(SearchImageDataModel.self, from: data) else { throw PixabayAPIError.noData }
        
        self.imageDatas = decoded.hits
    }
}

searchImagesメソッドのエラー対応を行なったことで、コード全体を短くまとめることができました。
do – catch 文は ImageLoader クラスを呼び出す ContentView.swift に一度書くだけでエラー処理を全て行えることとなり、コード自体も見通しがよくなります。

タイトルとURLをコピーしました