ジェネリクスを使ったWebAPI経由でのデータ(JSONフォーマットによるデータ)取得のクラスメソッドを作ってみました。
ネット接続(HTTP接続)やJSONデコードには外部のフレームワークはつかっていません。AlamofireやSwiftyJSONを使っても良いのですが、とりあえずはシンプルな形にしてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
struct WebAPI<T: Codable> { static func getData(urlAsString url: String, completion: @escaping (T?, String?) -> Void){ guard let reqURL = URL(string: url) else { completion(nil, "Illegal URL: \(url)") return } var request = URLRequest(url: reqURL) request.httpMethod = "GET" let task = URLSession.shared.dataTask(with: request) { data, response, error in if let jsonData = data, error == nil { do { let encoded = try JSONDecoder().decode(T.self, from: jsonData) DispatchQueue.main.async(execute: { completion(encoded, nil) }) } catch { DispatchQueue.main.async { completion(nil, error.localizedDescription) } } } else { DispatchQueue.main.async { completion(nil, error?.localizedDescription) } } } task.resume() } } |
Swift5でのJSON形式のダウンロードでは、ダウンロードしたいJSONのフォーマットに従って構造体を作り、その構造体をJSONDecoder().decodeで指定することで希望するデータを取得することができます。
ただ、実際にデータを取得するURLSession.shared.dataTaskの処理はクロージャで行っているため、取得したデータを使うためにはもうひと工夫必要となります。
いくつか方法がありそうですが、ここでは一般的に行われているように、ダウンロードしたデータを処理するためのクロージャを指定することで、必要な処理を異なったスコープで行えるようにしてみました。
また、ジェネリクスを使うことで、JSONフォーマットが異なっても汎用的に使えるようにしてみました。
以下は、日本におけるコロナウィルス感染者数の情報を提供しているサイトから、国内の発生事例をJSONフォーマットで取得する例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
struct Covid19Total : Codable { var confirming : Int var date : Int var death : Int var discharge : Int var hospitalize : Int var mild : Int var pcr : Int var positive : Int var severe : Int var symptom : Int var symptomless : Int var symtomConfirming : Int var waiting : Int } WebAPI<Covid19Total>.getData(urlAsString: "https://covid19-japan-web-api.now.sh/api/v1/total", completion: { data, error in if let data = data { dump(data) } else if let error = error { print("error:", error) } else { print("Unknown error") } }) |
クラスの型としてCodableに準拠した構造体を指定し、クラスメソッド(getData(urlAsString: completion:))でデータを取得します。第一引数は接続先のURLをString型で、第二引数では取得完了後の処理をクロージャとして渡します。
アプリの詳細をきめていくと、接続先のサーバが返すステータスコードなどに応じてJSONのフォーマットを変更したいなど、さらに細かい処理が必要になってくるため、このような汎用的なコードでは対応できない場合も出てきますが、とりあえずプロトタイプを作るような場合には使えるのではないかとおもいます。
次の例は同じサイトから都道府県別の感染者数リストを取得する例です。この例では、トップレベルで配列になっているJSONフォーマットを取得するため、構造体を配列の形で渡す必要がありますが、それにも同じ方法で対処できることがわかるかと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
struct Covid19Case : Codable { var cases : Int var deaths : Int var id : Int var lat : Float var lng : Float var nameEn : String var nameJa : String enum CodingKeys: String, CodingKey { case cases case deaths case id case lat case lng case nameEn = "name_en" case nameJa = "name_ja" } } WebAPI<[Covid19Case]>.getData(urlAsString: "https://covid19-japan-web-api.now.sh/api/v1/prefectures", completion: { data, error in if let data = data { dump(data) } else if let error = error { print("error:", error) } else { print("Unknown error") } }) |
ジェネリクスを使うのはほぼ初めてなのでこれで良いのか、という疑問は残っていますが、とりあえず最低限の範囲で汎用化することはできました。