如何在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
相关产品推荐
相关产品推荐

