如何从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案例的重载方法,让编译器根据传入的路由自动推断返回类型。
实现步骤
- 在
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) } }
- 调用方式(编译器自动推断返回类型):
// 创建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枚举结构,但路由案例较多时会产生大量重载方法,维护成本较高。
方案二:协议关联类型(更优雅的长期方案)
通过定义带有关联类型的协议,让每个路由类型绑定对应的返回类型,从根源上实现类型推断。
实现步骤
- 定义路由协议,关联对应的返回类型:
protocol APIRoute { associatedtype Response: Decodable // 补充路由所需的基础信息(根据原有逻辑调整) var path: String { get } var method: HTTPMethod { get } // 其他参数:headers、body等 }
- 替换原有嵌套枚举为遵循
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 }
- 修改
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) }
- 调用方式(编译器自动推断返回类型):
// 创建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
相关产品推荐
相关产品推荐

