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

如何在Swift协议/扩展中注入运行时依赖参数?

解决Swift协议/扩展中注入运行时依赖参数的问题

问题背景

我按照教程构建Swift 5.5异步通用网络层,已完成网络管理器,但对接更多API时发现,部分API需要运行时获取的令牌、请求体或请求头数据。原教程中Endpoint协议和MoviesEndpoint扩展里的访问令牌是硬编码的,无法动态配置。我尝试把body属性改成带参数的函数,但因为MoviesEndpoint遵循Endpoint协议,触发了“Protocol Methods must not have bodies”编译错误,想知道怎么向Swift协议/扩展的方法注入运行时依赖参数。

原核心代码:

protocol Endpoint {
    var scheme: String { get }
    var host: String { get }
    var version: String? { get }
    var path: String { get }
    var method: RequestMethod { get }
    var queryItems: [String: String]? { get }
    var header: [String: String]? { get }
    var body: [String: String]? { get }
}

extension MoviesEndpoint: Endpoint {
    var path: String {
        switch self {
        case .topRated:
            return "/3/movie/top_rated"
        case .movieDetail(let id):
            return "/3/movie/\(id)"
        }
    }

    var method: RequestMethod {
        switch self {
        case .topRated, .movieDetail:
            return .get
        }
    }

    var header: [String: String]? {
        // Access Token to use in Bearer header
        let accessToken = "insert your access token here"
        switch self {
        case .topRated, .movieDetail:
            return [
                "Authorization": "Bearer \(accessToken)",
                "Content-Type": "application/json;charset=utf-8"
            ]
        }
    }
    
    var body: [String: String]? {
        switch self {
        case .topRated, .movieDetail:
            return nil
        }
    }
}

解决方案

方法1:修改Endpoint协议,将属性改为带参数的方法

把原协议里的header、body等静态属性替换成接受动态参数的方法,让实现类/枚举能根据传入的运行时数据返回对应值。

修改后的协议:

protocol Endpoint {
    var scheme: String { get }
    var host: String { get }
    var version: String? { get }
    var path: String { get }
    var method: RequestMethod { get }
    var queryItems: [String: String]? { get }
    // 将属性改为带参数的方法
    func header(with token: String?) -> [String: String]?
    func body(with parameters: [String: String]?) -> [String: String]?
}

更新MoviesEndpoint扩展实现:

extension MoviesEndpoint: Endpoint {
    // ... 其他属性(scheme、host、version等)保持不变
    
    func header(with token: String?) -> [String: String]? {
        guard let accessToken = token else { return nil }
        switch self {
        case .topRated, .movieDetail:
            return [
                "Authorization": "Bearer \(accessToken)",
                "Content-Type": "application/json;charset=utf-8"
            ]
        case .test:
            return ["Content-Type": "application/json;charset=utf-8"]
        }
    }
    
    func body(with parameters: [String: String]?) -> [String: String]? {
        switch self {
        case .topRated, .movieDetail:
            return nil
        case .test:
            return parameters
        }
    }
}

调用时传入运行时获取的参数:

// 从UserDefaults或网络请求获取令牌
let runtimeToken = UserDefaults.standard.string(forKey: "userAccessToken")
let endpoint = MoviesEndpoint.topRated
let requestHeaders = endpoint.header(with: runtimeToken)
let requestBody = endpoint.body(with: ["key": "runtimeValue"])

方法2:给枚举添加关联值传递动态参数

如果你的Endpoint实现是枚举(比如MoviesEndpoint),直接给枚举case添加关联值,把运行时需要的参数内嵌进去,无需修改协议结构。

更新MoviesEndpoint枚举:

enum MoviesEndpoint {
    case topRated
    case movieDetail(id: Int)
    // 带动态请求体的case
    case test(body: [String: String]?)
    // 带动态令牌和用户ID的受保护API case
    case protectedAPI(token: String, userId: String)
}

更新扩展实现:

extension MoviesEndpoint: Endpoint {
    // ... 其他属性保持不变
    
    var header: [String: String]? {
        switch self {
        case .topRated, .movieDetail:
            // 这里也可以从外部获取令牌,比如UserDefaults
            let accessToken = UserDefaults.standard.string(forKey: "userAccessToken") ?? ""
            return [
                "Authorization": "Bearer \(accessToken)",
                "Content-Type": "application/json;charset=utf-8"
            ]
        case .protectedAPI(let token, _):
            // 使用关联值传入的动态令牌
            return [
                "Authorization": "Bearer \(token)",
                "Content-Type": "application/json;charset=utf-8"
            ]
        case .test:
            return ["Content-Type": "application/json;charset=utf-8"]
        }
    }
    
    var body: [String: String]? {
        switch self {
        case .topRated, .movieDetail:
            return nil
        case .test(let body):
            // 使用关联值传入的动态请求体
            return body
        case .protectedAPI(_, let userId):
            return ["user_id": userId]
        }
    }
}

调用时直接传入动态参数:

// 假设从网络请求获取到令牌
let runtimeToken = fetchTokenFromAuthAPI()
let endpoint = MoviesEndpoint.protectedAPI(token: runtimeToken, userId: "12345")

方法3:使用依赖注入,封装运行时配置

如果需要更灵活的全局配置,可以创建一个配置结构体,把所有运行时依赖封装进去,再通过Endpoint协议传递。

定义配置结构体:

struct APIConfig {
    var accessToken: String?
    var defaultHeaders: [String: String]?
    var timeoutInterval: TimeInterval = 30
}

修改Endpoint协议:

protocol Endpoint {
    var scheme: String { get }
    var host: String { get }
    var version: String? { get }
    var path: String { get }
    var method: RequestMethod { get }
    var queryItems: [String: String]? { get }
    // 添加配置属性
    var config: APIConfig { get }
    func header() -> [String: String]?
    func body() -> [String: String]?
}

更新MoviesEndpoint枚举:

enum MoviesEndpoint {
    case topRated(config: APIConfig)
    case movieDetail(id: Int, config: APIConfig)
    case test(body: [String: String]?, config: APIConfig)
    
    // 实现协议要求的config属性
    var config: APIConfig {
        switch self {
        case .topRated(let config), .movieDetail(_, let config), .test(_, let config):
            return config
        }
    }
}

更新扩展实现:

extension MoviesEndpoint: Endpoint {
    // ... 其他属性保持不变
    
    func header() -> [String: String]? {
        var headers = config.defaultHeaders ?? [:]
        if let token = config.accessToken {
            headers["Authorization"] = "Bearer \(token)"
        }
        return headers
    }
    
    func body() -> [String: String]? {
        switch self {
        case .topRated, .movieDetail:
            return nil
        case .test(let body, _):
            return body
        }
    }
}

调用时传入配置:

// 构建运行时配置
let runtimeConfig = APIConfig(
    accessToken: fetchTokenFromAuthAPI(),
    defaultHeaders: ["Content-Type": "application/json;charset=utf-8"]
)
let endpoint = MoviesEndpoint.topRated(config: runtimeConfig)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 15:50:32