Swift带关联类型协议的final类多态:Apollo GraphQLMutation通用返回方案
问题根源
Swift中带关联类型的协议(PAT)无法直接作为返回值类型使用,仅能作为泛型约束。Apollo的GraphQLOperation协议定义了Data关联类型,其子协议GraphQLMutation自然也属于PAT,这是触发编译错误的核心原因。Apollo自动生成的final类不影响泛型、类型擦除方案的使用,final仅限制类不可被继承,只要类实现了GraphQLMutation协议即可被正常处理。
方案一:泛型约束工厂(推荐,适用于绝大多数场景)
通过泛型约束让调用方指定Mutation类型,编译器可自动推导关联类型,完全保留编译期类型安全,无需额外类型转换。
实现代码
func getMutation<T: GraphQLMutation>(for type: T.Type, parameters: [String: Any]) -> T { switch type { case is CreateUserMutation.Type: return CreateUserMutation( name: parameters["name"] as! String, age: parameters["age"] as! Int ) as! T case is UpdatePostMutation.Type: return UpdatePostMutation( postId: parameters["postId"] as! String, content: parameters["content"] as! String ) as! T // 其他Mutation类型按需补充case default: fatalError("未匹配到对应的Mutation类型") } }
调用示例
// 直接传入生成的Mutation类即可,返回值可直接喂给ApolloClient的perform方法 let mutation = getMutation(for: CreateUserMutation.self, parameters: ["name": "张三", "age": 20]) apollo.perform(mutation: mutation) { result in // result类型自动推导为GraphQLResult<CreateUserMutation.Data>,无需手动转换 switch result { case .success(let res): // 直接处理返回结果 case .failure(let error): // 处理错误 } }
优缺点
- 优点:实现成本最低,无性能损耗,完全保留类型安全
- 缺点:调用时必须明确指定Mutation类型,不支持运行时动态决定调用的Mutation
方案二:类型擦除工厂(适用于运行时动态选Mutation的场景)
如果需要根据运行时参数动态决定返回的Mutation类型,可以通过类型擦除封装GraphQLMutation的执行逻辑,抹平关联类型差异。
实现代码
// 自定义类型擦除结构体 struct AnyGraphQLMutation { private let _perform: (ApolloClient, @escaping (Result<GraphQLSelectionSet, Error>) -> Void) -> Cancellable init<T: GraphQLMutation>(_ mutation: T) { _perform = { client, completion in client.perform(mutation: mutation) { result in switch result { case .success(let graphQLResult): if let data = graphQLResult.data { completion(.success(data)) } else if let errors = graphQLResult.errors, !errors.isEmpty { let errMsg = errors.map{ $0.localizedDescription }.joined(separator: "\n") completion(.failure(NSError(domain: "ApolloMutation", code: -1, userInfo: [NSLocalizedDescriptionKey: errMsg]))) } else { completion(.failure(NSError(domain: "ApolloMutation", code: -2, userInfo: [NSLocalizedDescriptionKey: "空返回"]))) } case .failure(let error): completion(.failure(error)) } } } } /// 直接调用该方法执行Mutation,无需再传入ApolloClient的perform func perform(with client: ApolloClient, completion: @escaping (Result<GraphQLSelectionSet, Error>) -> Void) -> Cancellable { return _perform(client, completion) } } // 工厂方法 func getMutation(by action: String, parameters: [String: Any]) -> AnyGraphQLMutation { switch action { case "createUser": let mutation = CreateUserMutation( name: parameters["name"] as! String, age: parameters["age"] as! Int ) return AnyGraphQLMutation(mutation) case "updatePost": let mutation = UpdatePostMutation( postId: parameters["postId"] as! String, content: parameters["content"] as! String ) return AnyGraphQLMutation(mutation) // 其他Mutation按需补充case default: fatalError("未匹配到对应的Mutation类型") } }
调用示例
// 运行时根据字符串动态选择Mutation let mutation = getMutation(by: "createUser", parameters: ["name": "张三", "age": 20]) mutation.perform(with: apollo) { result in switch result { case .success(let data): // 需要手动把通用结果转为对应Mutation的Data类型 if let userData = data as? CreateUserMutation.Data { // 处理返回结果 } case .failure(let error): // 处理错误 } }
优缺点
- 优点:支持运行时动态选择Mutation,可将不同类型的Mutation实例存入同一集合统一管理
- 缺点:丢失部分编译期类型安全,返回结果需要手动转换类型
内容的提问来源于stack exchange,提问作者Daniel Kaplan
相关产品推荐
相关产品推荐

