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

如何从Route枚举自动推断API请求的返回类型?

问题描述

我们App中定义了涵盖所有后端接口的Route枚举,结构如下:

enum Route {
  case todo(TodoRoute)
  case event(EventRoute)
  case userDetails

  enum TodoRoute {
    case create
    case delete(Todo)
  }

  enum EventRoute {
    case list
    case edit(Event)
  }
}

同时ApiClient实现了两个泛型request方法用于发起请求并解码数据:

public func request<A: Decodable>(
    _ route: Route,
    as: A.Type
) async throws -> A {
    let (data, _) = try await self.request(route)
    do {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        return try decoder.decode(A.self, from: data)
    } catch {
        throw error
    }
}

public func request<A: Decodable>(
    _ route: Route
) async throws -> A {
    let (data, _) = try await self.request(route)
    do {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        return try decoder.decode(A.self, from: data)
    } catch {
        throw error
    }
}

当前调用时必须显式指定返回类型:

// 方式1
let result = try await apiClient.request(.userDetails, as: UserDetails.self)

// 方式2
let result: EventList = try await apiClient.request(.event(.list))

由于每个Route枚举案例和返回类型一一对应,希望仅传入Route就能让编译器自动推断返回类型,比如:

let result = try await apiClient.request(.event(.list))

请问是否可以实现?若可行,该如何做?


解决方案

完全可以实现,下面提供两种实用方案:

方案一:方法重载(适配原有枚举结构)

通过给ApiClient添加对应每个Route案例的重载方法,让编译器根据传入的路由自动推断返回类型。

实现步骤

  1. 在ApiClient的扩展中添加重载方法,每个方法对应一个具体的路由案例,并指定固定的返回类型:
extension ApiClient {
    // TodoRoute相关
    public func request(_ route: Route.TodoRoute) async throws -> Todo {
        try await self.request(.todo(route), as: Todo.self)
    }
    
    // EventRoute拆分不同案例(因list和edit返回类型不同)
    public func request(_ route: Route.EventRoute.list) async throws -> EventList {
        try await self.request(.event(.list), as: EventList.self)
    }
    
    public func request(_ route: Route.EventRoute.edit) async throws -> Event {
        let event = (route as Route.EventRoute.edit).event
        try await self.request(.event(.edit(event)), as: Event.self)
    }
    
    // userDetails案例
    public func request(_ route: Route.userDetails) async throws -> UserDetails {
        try await self.request(.userDetails, as: UserDetails.self)
    }
}
  1. 调用方式(编译器自动推断返回类型):
// 创建Todo
let newTodo = try await apiClient.request(Route.TodoRoute.create)

// 获取事件列表
let eventList = try await apiClient.request(Route.EventRoute.list)

// 获取用户详情
let userInfo = try await apiClient.request(Route.userDetails)

优缺点:无需修改原有Route枚举结构,但路由案例较多时会产生大量重载方法,维护成本较高。


方案二:协议关联类型(更优雅的长期方案)

通过定义带有关联类型的协议,让每个路由类型绑定对应的返回类型,从根源上实现类型推断。

实现步骤

  1. 定义路由协议,关联对应的返回类型:
protocol APIRoute {
    associatedtype Response: Decodable
    // 补充路由所需的基础信息(根据原有逻辑调整)
    var path: String { get }
    var method: HTTPMethod { get }
    // 其他参数:headers、body等
}
  1. 替换原有嵌套枚举为遵循APIRoute的结构体,每个结构体对应一个具体路由:
// 创建Todo
struct TodoCreateRoute: APIRoute {
    typealias Response = Todo
    let path = "/todos"
    let method: HTTPMethod = .post
}

// 删除Todo
struct TodoDeleteRoute: APIRoute {
    let todo: Todo
    typealias Response = Todo
    var path: String { "/todos/\(todo.id)" }
    let method: HTTPMethod = .delete
}

// 获取事件列表
struct EventListRoute: APIRoute {
    typealias Response = EventList
    let path = "/events"
    let method: HTTPMethod = .get
}

// 编辑事件
struct EventEditRoute: APIRoute {
    let event: Event
    typealias Response = Event
    var path: String { "/events/\(event.id)" }
    let method: HTTPMethod = .put
}

// 获取用户详情
struct UserDetailsRoute: APIRoute {
    typealias Response = UserDetails
    let path = "/user/details"
    let method: HTTPMethod = .get
}
  1. 修改ApiClient的request方法,适配APIRoute协议:
public func request<R: APIRoute>(_ route: R) async throws -> R.Response {
    // 根据route的path、method等构建URLRequest(替换原有Route转换逻辑)
    var urlRequest = URLRequest(url: URL(string: baseURL + route.path)!)
    urlRequest.httpMethod = route.method.rawValue
    // 补充headers、body等设置
    
    let (data, _) = try await URLSession.shared.data(for: urlRequest)
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .iso8601
    return try decoder.decode(R.Response.self, from: data)
}
  1. 调用方式(编译器自动推断返回类型):
// 创建Todo
let newTodo = try await apiClient.request(TodoCreateRoute())

// 获取事件列表
let eventList = try await apiClient.request(EventListRoute())

// 编辑事件
let updatedEvent = try await apiClient.request(EventEditRoute(event: myEvent))

优缺点:代码结构更清晰,扩展性更强,新增路由只需添加新的结构体即可;但需要重构原有Route枚举的逻辑,适合长期维护的项目。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 16:11:16