[iOS] APIエラーハンドリングとリトライ処理

機能 内容
エラーハンドリング 通信失敗 / デコード失敗 を正しく処理する
ステータスコード確認 API が成功か失敗かを確認する
リトライ処理 失敗したらもう一度試す

### エラーの定義

enum APIError: Error {
    case invalidURL
    case networkError
    case serverError(Int) // ステータスコード付き
    case decodeError
}

呼び出し

import Foundation

struct WeatherResponse: Codable {
    struct CurrentWeather: Codable {
        let temperature: Double
        let windspeed: Double
    }
    let current_weather: CurrentWeather
}

class WeatherAPI {
    func fetchWeather() async throws -> WeatherResponse {
        guard let url = URL(string: "https://api.open-meteo.com/v1/forecast?latitude=35.6895&longitude=139.6917&current_weather=true") else {
            throw APIError.invalidURL
        }
        
        let (data, response) = try await URLSession.shared.data(from: url)
        
        // ステータスコードチェック
        if let httpResponse = response as? HTTPURLResponse,
           !(200..<300).contains(httpResponse.statusCode) {
            throw APIError.serverError(httpResponse.statusCode)
        }

        // JSON デコード
        do {
            return try JSONDecoder().decode(WeatherResponse.self, from: data)
        } catch {
            throw APIError.decodeError
        }
    }
}
func fetchWeatherWithRetry() async {
    let api = WeatherAPI()
    
    for attempt in 1...3 {
        do {
            let result = try await api.fetchWeather()
            print("成功: \(result.current_weather.temperature)°C")
            return   // 成功したら終了
        } catch APIError.serverError(let code) {
            print("サーバーエラー (\(code)) → リトライ(\(attempt))")
        } catch {
            print("その他エラー → リトライ(\(attempt))")
        }
        try? await Task.sleep(nanoseconds: 1_000_000_000) // 1秒待つ
    }

    print("3回試したが失敗しました 😢")
}
import SwiftUI

struct ContentView: View {
    @State private var temperature: String = "__"
    
    var body: some View {
        VStack(spacing: 20) {
            Text("気温: \(temperature)°C")
                .font(.title)
            
            Button("天気を取得(リトライ付き)") {
                Task {
                    await fetchWeatherUI()
                }
            }
        }
        .padding()
    }

    func fetchWeatherUI() async {
        let api = WeatherAPI()
        
        for _ in 1...3 {
            do {
                let result = try await api.fetchWeather()
                await MainActor.run {
                    temperature = String(result.current_weather.temperature)
                }
                return
            } catch {
                print("失敗 → 再試行")
                try? await Task.sleep(nanoseconds: 800_000_000)
            }
        }
        await MainActor.run {
            temperature = "取得失敗"
        }
    }
}