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

Swift中能否从路由关联类型推断泛型函数返回类型?

解决方案:编译期绑定路由与响应类型,消除冗余传参

核心思路是通过协议关联类型,让每个路由在编译期就和对应的响应解码类型绑定,编译器可以自动推断返回类型,同时保留动态传递路由参数的能力。

1. 重构核心路由协议

修改NetLibRoute协议,添加关联类型ResponseType并约束为Decodable,替代原来的decodedType属性:

public protocol NetLibRoute {
    associatedtype ResponseType: Decodable
    var asURL: URL { get throws }
}

这个关联类型会在编译期绑定每个路由对应的响应类型,为自动推断提供依据。

2. 重写网络请求方法

更新NetLib.get方法,利用路由的关联类型自动推断返回类型,无需手动传入类型参数:

public static func get<Route: NetLibRoute>(route: Route) async throws -> Route.ResponseType {
    // 1. 发起网络请求获取数据
    let (data, _) = try await URLSession.shared.data(from: route.asURL)
    // 2. 自动使用路由绑定的类型解码
    return try JSONDecoder().decode(Route.ResponseType.self, from: data)
}

3. 宿主App实现路由(两种可选方式)

方式一:嵌套结构体聚合路由(推荐)

将每个路由实现为嵌套在枚举内的结构体,既保留枚举的聚合特性,又让每个路由拥有独立的关联类型:

// 先定义解码模型
struct Person: Decodable {
    let name: String
    let height: String
    // 其他字段...
}

// 路由容器枚举
enum Routes {
    // /api/people/[id] 路由的具体实现
    struct People: NetLibRoute {
        typealias ResponseType = Person
        let personID: Int?
        
        var asURL: URL {
            let host = "https://swapi.dev/"
            let path = personID != nil ? "api/people/\(personID!)" : "api/people/"
            guard let url = URL(string: host + path) else {
                preconditionFailure("Invalid URL constructed for People route")
            }
            return url
        }
    }
    
    // 可添加更多路由,比如星舰路由
    struct Starship: NetLibRoute {
        typealias ResponseType = StarshipModel
        let starshipID: Int
        
        var asURL: URL {
            let host = "https://swapi.dev/"
            guard let url = URL(string: host + "api/starships/\(starshipID)") else {
                preconditionFailure("Invalid URL constructed for Starship route")
            }
            return url
        }
    }
}

调用示例

编译器会自动根据传入的路由推断返回类型,无需手动指定:

// 获取ID为1的Person
let person = try await NetLib.get(route: Routes.People(personID: 1))
// 获取所有Person列表
let peopleList = try await NetLib.get(route: Routes.People(personID: nil))
// 获取ID为2的星舰
let starship = try await NetLib.get(route: Routes.Starship(starshipID: 2))

方式二:保留枚举case的实现

如果你更倾向于使用枚举case来定义路由,可以通过枚举的makeRoute()方法返回对应类型的路由实例:

enum Routes {
    case people(Int?)
    case starship(Int)
    
    func makeRoute() -> some NetLibRoute {
        switch self {
        case let .people(id):
            return PeopleRoute(personID: id)
        case let .starship(id):
            return StarshipRoute(starshipID: id)
        }
    }
    
    // 内部路由实现(私有隐藏细节)
    private struct PeopleRoute: NetLibRoute {
        typealias ResponseType = Person
        let personID: Int?
        
        var asURL: URL {
            // 同方式一的URL构建逻辑
            let host = "https://swapi.dev/"
            let path = personID != nil ? "api/people/\(personID!)" : "api/people/"
            guard let url = URL(string: host + path) else {
                preconditionFailure("Invalid URL constructed for People route")
            }
            return url
        }
    }
    
    private struct StarshipRoute: NetLibRoute {
        typealias ResponseType = StarshipModel
        let starshipID: Int
        
        var asURL: URL {
            // 同方式一的URL构建逻辑
            let host = "https://swapi.dev/"
            guard let url = URL(string: host + "api/starships/\(starshipID)") else {
                preconditionFailure("Invalid URL constructed for Starship route")
            }
            return url
        }
    }
}

调用示例

只需在枚举case后调用.makeRoute()即可:

let person = try await NetLib.get(route: Routes.people(1).makeRoute())
let starship = try await NetLib.get(route: Routes.starship(2).makeRoute())

方案优势

  • 编译期安全:所有类型推断都在编译阶段完成,没有运行时强制类型转换或解包的风险
  • 无冗余传参:无需重复传入解码类型,减少人为错误
  • 支持动态参数:路由的动态参数(如personID)可以灵活传递
  • 可扩展性:新增路由只需添加对应的结构体实现,不影响现有逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 01:00:59