共通プロトコルを使用して選ぶ
|
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import SwiftUI protocol aabb: Codable, Identifiable {} // 構造体をまとめる struct aaaa: aabb { var id: UUID = UUID() let a: String let b: String } struct bbbb: aabb { var id: UUID = UUID() let a: String let b: String } func test(c: Int) -> any aabb { //ここで使用する構造体を返せる switch c { case 1: return aaaa(a: "111", b: "222") case 2: return bbbb(a: "333", b: "444") default: return aaaa(a: "-", b: "-") } } struct UiTest: View { @State var day1: Date? = nil @State var tt: String = "" var body: some View { VStack{ Button(action: { if day1 == nil{ let x = test(c: 1) // struct aaaaが入っている switch x { case let a as aaaa: //ダウンキャスト必須 tt = a.a case let b as bbbb: tt = b.a default : tt = "---" } } else { tt = "ccc" } }){ Text("aaa:\(tt)") } } } } #Preview { UiTest() } |

共通プロトコルを用いたAPIレスポンスの処理
APIから受け取った生データ(Data)を、内容に応じて異なるCodable構造体に変換する際の処理フローです。
1. プロトコルと構造体の定義
デコード対象となる全ての構造体(WeatherDataとAstroData)に、空のプロトコルであるAPIResponseDataを準拠させます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
protocol APIResponseData: Codable {} // Codableを継承させると便利 struct WeatherData: APIResponseData { let city: String let temperature: Int } struct AstroData: APIResponseData { let sunrise: String let sunset: String } // データの内容を判別するための構造体を定義することも多い struct ResponseCheck: Codable { let type: String? // 例: "weather" または "astro" } |
💡 ポイント: APIResponseDataプロトコルに**Codableを継承**させておくと、各構造体で個別にCodableを宣言する必要がなくなり、すっきりします。
2. 判別とデコードを実行する関数
APIから受け取ったDataを、まず判別用の構造体で一度デコードし、返す構造体を決定します。
|
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 |
/// 受け取ったDataを判別し、適切な構造体にデコードして返します。 func decodeAPIResponse(data: Data) -> APIResponseData? { let decoder = JSONDecoder() // 1. 最初に、判別用の簡易な構造体でデコードを試みる guard let check = try? decoder.decode(ResponseCheck.self, from: data), let type = check.type else { // 判別用のキーがない、またはデコードできない場合は処理を終了 print("Error: レスポンスタイプを判別できませんでした。") return nil } // 2. typeの値に基づいて、適切な構造体でデコードし直す switch type { case "weather": // 天気データとしてデコードを試みる if let weather = try? decoder.decode(WeatherData.self, from: data) { return weather // WeatherData (APIResponseDataとして) を返す } case "astro": // 天文データとしてデコードを試みる if let astro = try? decoder.decode(AstroData.self, from: data) { return astro // AstroData (APIResponseDataとして) を返す } default: print("Error: 未知のレスポンスタイプです (\(type))。") return nil } return nil } |
3. 利用時のダウンキャスト
関数から返ってきたAPIResponseData?型の値は、そのままではcityやsunriseといった具体的なプロパティにアクセスできません。利用する側で、as? を使って元の具体的な型にダウンキャストし、安全に処理を分岐させる必要があります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 仮想的なJSONデータ(実際はAPIから取得) let weatherJsonData: Data = """ {"type": "weather", "city": "Tokyo", "temperature": 25} """.data(using: .utf8)! if let response = decodeAPIResponse(data: weatherJsonData) { // ダウンキャストとパターンマッチング if let weather = response as? WeatherData { print("✅ 天気データがデコードされました。都市: \(weather.city), 気温: \(weather.temperature)℃") } else if let astro = response as? AstroData { print("✅ 天文データがデコードされました。日の出: \(astro.sunrise), 日の入り: \(astro.sunset)") } else { print("⚠️ 未知のAPIResponseDataです。") } } |
列挙型(Enum)に構造体を関連値として持たせる
返される構造体の種類が限定的で少ない場合、列挙型に関数で返す構造体を関連値として持たせる方法も非常に有用です。
|
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 33 34 35 36 |
// 構造体の定義 (この例ではプロトコル準拠は不要) struct UserInfo: Codable { let name: String } struct PostList: Codable { let count: Int } // 複数の型を保持する列挙型を定義 enum APIResult { case userInfo(UserInfo) case postList(PostList) case error(Error) } // 戻り値の型をこの列挙型にする func processAPI(data: Data, type: String) -> APIResult { if type == "user" { if let decoded = try? JSONDecoder().decode(UserInfo.self, from: data) { return .userInfo(decoded) } } else if type == "posts" { if let decoded = try? JSONDecoder().decode(PostList.self, from: data) { return .postList(decoded) } } return .error(NSError(domain: "DecodeError", code: 0)) } // 使い方 let result = processAPI(data: /*...*/, type: "user") switch result { case .userInfo(let user): print("ユーザー名: \(user.name)") case .postList(let posts): print("投稿数: \(posts.count)") case .error(let err): print("エラー: \(err.localizedDescription)") } |
利点: switch文によるパターンマッチングで、ダウンキャストなしで、型安全に全ての可能性を処理できます。
欠点: 返す可能性のある構造体の数が増えるほど、enumの定義やswitch文が長くなります。
Any または AnyObject (非推奨)
Swiftの全ての型を表す Any や、クラスインスタンスを表す AnyObject を戻り値の型にすることも可能ですが、型安全性が失われるため、特殊な理由がない限り推奨されません。
|
1 2 3 4 5 |
func unsafeDecodeJSON(data: Data) -> Any? { // ... 判別ロジック ... let decodedObject: Any = PostList(count: 5) // 構造体を返す return decodedObject } |
利点: どんな型でも返すことができます。
欠点: 利用時に必ず as? でダウンキャストする必要があり、キャストに失敗すると実行時エラーになるリスクが高く、コンパイラの恩恵(型チェック)を全く受けられません。

コメント