具有最终类的多态性,可在 swift 中实现关联类型协议

Posted

技术标签:

【中文标题】具有最终类的多态性,可在 swift 中实现关联类型协议【英文标题】:Polymorphism with a final class that implements an associatedtype protocol in swift 【发布时间】:2021-12-22 11:03:59 【问题描述】:

我正在使用Apollo v0.49.0。它是一个用于调用 graphQL 端点的库,它执行此操作的方式是在编译代码之前生成代码。

在谈论生成的代码之前,我想先谈谈生成的代码实现了什么。对于这个问题,相关的是GraphQLMutation。这是它的样子:

public enum GraphQLOperationType 
  case query
  case mutation
  case subscription


public protocol GraphQLOperation: AnyObject 
  var operationType: GraphQLOperationType  get 

  var operationDefinition: String  get 
  var operationIdentifier: String?  get 
  var operationName: String  get 

  var queryDocument: String  get 

  var variables: GraphQLMap?  get 

  associatedtype Data: GraphQLSelectionSet


public extension GraphQLOperation 
  var queryDocument: String 
    return operationDefinition
  

  var operationIdentifier: String? 
    return nil
  

  var variables: GraphQLMap? 
    return nil
  


public protocol GraphQLQuery: GraphQLOperation 
public extension GraphQLQuery 
  var operationType: GraphQLOperationType  return .query 


public protocol GraphQLMutation: GraphQLOperation 
public extension GraphQLMutation 
  var operationType: GraphQLOperationType  return .mutation 

这是the file 的 80%;最后 20% 与恕我直言无关。注意GraphQLMutation 是如何实现GraphQLOperation 的,而后者有一个associatedtype

该库根据您的 graphql 服务器端点生成类。它们是这样的:

public final class ConcreteMutation: GraphQLMutation 
    ...
    public struct Data: GraphQLSelectionSet 
        ...
    
    ...

据我所知(我是 Swift 新手),我无法控制到目前为止我提到的任何代码(除了分叉和修改它)。我可以在本地更改它们,但它们只会在每次重新生成时被覆盖。

要使用这些生成的类中的任何一个,我必须将它们传递给这个ApolloClient 函数(也是一个库类):

@discardableResult
public func perform<Mutation: GraphQLMutation>(mutation: Mutation,
                                                 publishResultToStore: Bool = true,
                                                 queue: DispatchQueue = .main,
                                                 resultHandler: GraphQLResultHandler<Mutation.Data>? = nil) -> Cancellable 
    return self.networkTransport.send(
      operation: mutation,
      cachePolicy: publishResultToStore ? .default : .fetchIgnoringCacheCompletely,
      contextIdentifier: nil,
      callbackQueue: queue,
      completionHandler:  result in
        resultHandler?(result)
      
    )
  

我不知道如何以通用方式处理ConcreteMutation。我希望能够像这样编写工厂函数:

extension SomeEnum 
   func getMutation<T: GraphQLMutation>() -> T 
        switch self 
            case .a:
                return ConcreteMutation1(first_name: value) as T
            case .b:
                return ConcreteMutation2(last_name: value) as T
            case .c:
                return ConcreteMutation3(bio: value) as T
            ...
        
    

这个函数在枚举中的事实与我无关:相同的代码可以在结构/类/任何东西中。 重要的是函数签名。我想要一个返回 GraphQLMutation 的工厂方法,该方法可以传递给 ApolloClient.perform()

因为我想不出办法来做这两件事,所以我最终写了一堆这样的函数:

func useConcreteMutation1(value) -> Void 
    let mutation = ConcreteMutation1(first_name: value)
    apolloClient.perform(mutation: mutation)


func useConcreteMutation2(value) -> Void 
    let mutation = ConcreteMutation2(last_name: value)
    apolloClient.perform(mutation: mutation)


...

这是很多重复的代码。

取决于我如何摆弄我的getMutation 签名——例如,&lt;T: GraphQLMutation&gt;() -&gt; T? 等——我可以编译 func,但是当我尝试将它传递给 ApolloClient.perform() 时会出现不同的编译错误.有人说“协议只能用作通用约束,因为它具有 Self 或关联的类型要求。”

我对此进行了很多研究,我的研究发现this article,但我认为如果实现关联类型的具体类是最终的,这不是一个选项?

真的很难弄清楚在这种情况下是否可以使用多态性。我可以找到很多关于你可以做什么的文章,但没有关于你不能做什么的文章。我的问题是:

如何编写 getMutation 以便它返回一个可以传递给 ApolloClient.perform() 的值?

【问题讨论】:

对于ConcreteMutation 之一,Data 的类型在哪里建立?它们是否使用定义Data 的类型别名生成 @ScottThompson 我更新了我的问题。它似乎是一个公共结构?我看错了吗?如果您想了解有关该实施的更多信息,请告诉我。 【参考方案1】:

你遇到的根本问题是这个函数签名:

func getMutation<T: GraphQLMutation>() -> T

模棱两可。之所以模棱两可,是因为 GraphQLMutation 具有关联类型 (Data),并且该信息不会出现在函数声明中的任何位置。

当你这样做时:

extension SomeEnum 
   func getMutation<T: GraphQLMutation>() -> T 
        switch self 
            case .a:
                return ConcreteMutation1(first_name: value) as T
            case .b:
                return ConcreteMutation2(last_name: value) as T
            case .c:
                return ConcreteMutation3(bio: value) as T
            ...
        
    

每个分支都可以有不同的类型。 ConcreteMutation1 可能有一个Data,即Dormouse,而ConcreteMutation3 可能有一个数据值,即IceCreamTruck。您可能可以告诉编译器忽略它,但稍后您会遇到问题,因为 DormouseIceCreamTruck 是两个大小非常不同的结构,编译器可能需要使用不同的策略将它们作为参数传递。

Apollo.perform 也是一个模板。编译器将根据该模板为您调用它的每种突变类型编写不同的函数。为了做到这一点,必须知道突变的完整类型签名,包括它的 Data 关联类型是什么。 responseHandler 回调应该能够处理Dormouse 大小的东西,还是需要能够处理IceCreamTruck 大小的东西?

如果编译器不知道,它就无法为responseHandler 设置正确的调用顺序。如果你试图通过一个为 Dormouse 大小的参数设计的回调调用序列来压缩 IceCreamTruck 大小的东西,就会发生糟糕的事情!

如果编译器不知道突变必须提供什么类型的Data,它就无法从模板中编写出正确版本的perform

如果您只将func getMutation&lt;T: GraphQLMutation&gt;() -&gt; T 的结果交给它,您已经消除了Data 类型是什么的证据,那么它不知道它应该写什么版本的perform

您试图隐藏Data 的类型,但您还希望编译器创建一个perform 函数,其中Data 的类型是已知的。你不能两者都做。

【讨论】:

有趣。既然不能两者都做,有没有比我现在更好的解决方案:一遍又一遍地复制和粘贴几乎相同的功能? 为什么会有中间函数useConcreteMutation2?它比apolloClient.perform(mutation: ConcreteMutation2(last_name: value)) 为您提供什么好处? (我怀疑如果函数没有占位符名称,值会更明显)。 这是一个很好的问题。我将不得不对此进行调查并回复您,但感谢您迄今为止的帮助。【参考方案2】:

也许你需要在associatedtype 上实现AnyGraphQLMutation 类型擦除。 网上有很多资源(类型擦除),我发现this one 非常详尽。

【讨论】:

感谢您的帮助,但您能告诉我如何使用示例中的代码来实现吗?我在这个问题上遇到的主要挑战是对是否存在解决方案进行肯定/否定。 如果你能明确一点,我会说:SomeClass 是什么,你在那个方法原型中切换了什么。 您的 (?) 协议 GraphQLOperation 是具有关联类型(顺便说一句不好的命名)Data 的协议,我看不到在其属性的任何地方使用它。最后一件事:GraphQLSelectionSet 是什么? 关于你的第一个问题, SomeClass 是一个枚举。我正在切换枚举的元素。关于你的第二个问题,抱歉,那不是我的代码,它是 Apollo 的。这是 GraphQLSelectionSet 的链接:github.com/apollographql/apollo-ios/blob/0.49.0/Sources/Apollo/… 感觉在这种情况下类型擦除无济于事,因为您将丢失所有有助于ApolloClient.perform 确定将什么结果传递回完成处理程序的类型信息。【参考方案3】:

我希望这会有所帮助:

class GraphQLQueryHelper

    static let shared = GraphQLQueryHelper()

    class func performGraphQLQuery<T:GraphQLQuery>(query: T, completion:@escaping(GraphQLSelectionSet) -> ())
    
        Network.shared.apollo().fetch(query: query, cachePolicy: .default)  (result) in
        
            switch result
            
            case .success(let res):
                if let data = res.data
                
                    completion(data)
                
                else if let error = res.errors?.first
                
                    if let dict = error["extensions"] as? NSDictionary
                    
                        switch dict.value(forKey: "code") as? String ?? "" 
                        case "invalid-jwt": /*Handle Refresh Token Expired*/
                        default: /*Handle error*/
                            break
                        
                    
                    else
                    
                        /*Handle error*/
                    
                
                else
                
                    /*Handle Network error*/
                
                break
            case .failure(let error):
                /*Handle Network error*/
                break
            
        
    
    
    class func peroformGraphQLMutation<T:GraphQLMutation>(mutation: T, completion:@escaping(GraphQLSelectionSet) -> ())
    
        Network.shared.apollo().perform(mutation: mutation)  (result) in
            switch result
            
            case .success(let res):
                if let data = res.data
                
                    completion(data)
                
                else if let error = res.errors?.first
                
                    if let dict = error["extensions"] as? NSDictionary
                    
                        switch dict.value(forKey: "code") as? String ?? "" 
                        case "invalid-jwt": /*Handle Refresh Token Expired*/
                        default: /*Handle error*/
                            break
                        
                    
                    else
                    
                        /*Handle error*/
                    
                
                else
                
                   /*Handle Network error*/
                
                break
            case .failure(let error):
                /*Handle error*/
                break
            
        
    

【讨论】:

谢谢!解释为什么这会有所帮助将更容易判断它是否有帮助。 ***.com/questions/66194191/…

以上是关于具有最终类的多态性,可在 swift 中实现关联类型协议的主要内容,如果未能解决你的问题,请参考以下文章

Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态)

js中实现多态

确定子类是不是具有在 Python 中实现的基类方法

rails 获取多态类的所有关联类

c++中啥是多态!

JSPatch 遇上swift