You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中复用API调用函数适配多端点及不同解码类的方法

复用SwiftUI API调用函数:动态适配不同解码类型

我开发了一个简易的iOS SwiftUI应用,需要调用7个API端点。已经写好用于API调用和数据响应解码的函数,要复用这个函数的话,需要替换端点URL字符串以及数据解码对应的类。传递URL很简单,但不知道怎么为每个端点向API函数传入不同的数据类。找了很多示例都不符合我的场景,考虑过用enum但不清楚具体实现方式。以下是当前的API调用函数和数据类代码:

func apiLoader(urlString: String) {
    
    // Create API URL
    //let url = URL(string: "https://api-hockey.p.rapidapi.com/timezone")
    let url = URL(string: urlString)
    guard let requestUrl = url else { fatalError() }
    
    // Create URL Request
    var request = URLRequest(url: requestUrl)
    
    // Specify Headers to use
    let headers = [
        "x-rapidapi-key": "REDACTED",
        "x-rapidapi-host": "REDACTED.p.rapidapi.com"
    ]
    
    // Specify HTTP Method to use
    request.httpMethod = "GET"
    request.allHTTPHeaderFields = headers
    
    // Send HTTP Request
    let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
        
        // Check if Error took place
        if let error = error {
            print("Error took place \(error)")
            return
        }
        
        // Convert HTTP Response Data to a simple String
        if let data = data.self {
            do {
                
                let dateFormatter = DateFormatter()
                dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
                
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                decoder.dateDecodingStrategy = .formatted(dateFormatter)
                
                let hockeyTimeZones = try decoder.decode(HockeyTimeZones.self, from: data)
                hockeyTimeZones.response!.forEach { timezone in print(timezone) }

                //return hockeyTimeZones

                
            } catch {
                print("Failed to load: \(error)")
                //return Void()
            }
            
        }
    }
    
    task.resume()

    //return HockeyTimeZones()
}
@Observable class HockeyTimeZones: ObservableObject, Codable {

  var get        : String?   = nil
  var parameters : [String]? = []
  var errors     : [String]? = []
  var results    : Int?      = nil
  var response   : [String]? = []

  enum CodingKeys: String, CodingKey {

    case _get        = "get"
    case _parameters = "parameters"
    case _errors     = "errors"
    case _results    = "results"
    case _response   = "response"
  
  }

    required init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)

    get        = try values.decodeIfPresent(String.self   , forKey: ._get        )
    parameters = try values.decodeIfPresent([String].self , forKey: ._parameters )
    errors     = try values.decodeIfPresent([String].self , forKey: ._errors     )
    results    = try values.decodeIfPresent(Int.self      , forKey: ._results    )
    response   = try values.decodeIfPresent([String].self , forKey: ._response   )
 
  }

  init() {

  }
}

方案一:泛型函数实现动态解码

核心思路是把apiLoader改成泛型函数,让它支持任意遵循Codable协议的类型,同时通过回调(completion handler)返回异步请求的结果。

修改后的函数代码:

func apiLoader<T: Codable>(urlString: String, completion: @escaping (Result<T, Error>) -> Void) {
    guard let requestUrl = URL(string: urlString) else {
        completion(.failure(NSError(domain: "Invalid URL", code: -1, userInfo: nil)))
        return
    }
    
    var request = URLRequest(url: requestUrl)
    let headers = [
        "x-rapidapi-key": "REDACTED",
        "x-rapidapi-host": "REDACTED.p.rapidapi.com"
    ]
    request.httpMethod = "GET"
    request.allHTTPHeaderFields = headers
    
    let task = URLSession.shared.dataTask(with: request) { data, _, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        
        guard let data = data else {
            completion(.failure(NSError(domain: "No data received", code: -2, userInfo: nil)))
            return
        }
        
        do {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
            
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            decoder.dateDecodingStrategy = .formatted(dateFormatter)
            
            let decodedData = try decoder.decode(T.self, from: data)
            completion(.success(decodedData))
        } catch {
            completion(.failure(error))
        }
    }
    
    task.resume()
}

调用方式示例

比如调用HockeyTimeZones类型的接口:

apiLoader(urlString: "https://api-hockey.p.rapidapi.com/timezone") { (result: Result<HockeyTimeZones, Error>) in
    switch result {
    case .success(let timezones):
        timezones.response?.forEach { print($0) }
        // 这里可以更新UI,比如把timezones赋值给@Observable变量
    case .failure(let error):
        print("加载失败:\(error)")
    }
}

如果是另一个接口,比如自定义的HockeyTeams类:

apiLoader(urlString: "https://api-hockey.p.rapidapi.com/teams") { (result: Result<HockeyTeams, Error>) in
    switch result {
    case .success(let teams):
        // 处理球队数据
    case .failure(let error):
        print("加载失败:\(error)")
    }
}

方案二:用Enum统一管理API端点

如果想更规范地管理7个API端点,可以定义一个枚举,把每个端点的URL和对应的解码类型绑定在一起,避免手动输入URL出错。

示例枚举代码:

enum APIEndpoint {
    case timezone
    case teams
    case players(Int) // 带参数的端点示例
    
    // 返回对应端点的URL
    var urlString: String {
        switch self {
        case .timezone:
            return "https://api-hockey.p.rapidapi.com/timezone"
        case .teams:
            return "https://api-hockey.p.rapidapi.com/teams"
        case .players(let teamId):
            return "https://api-hockey.p.rapidapi.com/teams/\(teamId)/players"
        }
    }
}

用Enum调用泛型函数

let endpoint = APIEndpoint.timezone
apiLoader(urlString: endpoint.urlString) { (result: Result<HockeyTimeZones, Error>) in
    // 处理结果
}

额外优化:简化数据类代码

你的HockeyTimeZones类有可以优化的地方:

  1. iOS 17+中,@Observable宏已经替代了ObservableObject,不需要同时继承
  2. 因为你用了decoder.keyDecodingStrategy = .convertFromSnakeCase,JSON中的蛇形键会自动映射到驼峰命名的属性,所以不需要自定义CodingKeys,代码可以简化:
@Observable class HockeyTimeZones: Codable {
    var get: String?
    var parameters: [String]? = []
    var errors: [String]? = []
    var results: Int?
    var response: [String]? = []
    
    // 不需要自定义init(from:),Codable会自动生成
    init() {}
}

内容的提问来源于stack exchange,提问作者Scott Carper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 14:17:14