简体   繁体   English

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

[英]Polymorphism with a final class that implements an associatedtype protocol in swift

I'm using Apollo v0.49.0 .我正在使用Apollo v0.49.0 It's a library for calling graphQL endpoints, and the way it does this is by generating code before you compile your code.它是一个用于调用 graphQL 端点的库,它执行此操作的方式是在编译代码之前生成代码。

Before I talk about the generated code, I'd like to talk about what the generated code implements.在说生成代码之前,我想先说一下生成代码实现了什么。 For this question, it's the GraphQLMutation that's relevant.对于这个问题,相关的是GraphQLMutation Here's what it looks like:这是它的样子:

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 }
}

This is 80% of the file ;这是文件的80%; the last 20% is irrelevant IMHO.最后 20% 是不相关的恕我直言。 Note how GraphQLMutation implements GraphQLOperation and the latter has an associatedtype .请注意GraphQLMutation如何实现GraphQLOperation并且后者有一个associatedtype

The library generates classes based on your graphql server endpoints.该库基于您的 graphql 服务器端点生成类。 Here's what they look like:这是它们的样子:

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

As far as I know (I'm new to Swift), I have no control over any of the code I've mentioned so far (other than forking the repo and modifying it).据我所知(我是 Swift 的新手),我无法控制到目前为止我提到的任何代码(除了分叉 repo 并修改它)。 I could change them locally, but they would just be overridden every time they were regenerated.我可以在本地更改它们,但是每次重新生成它们时它们都会被覆盖。

To use any of these generated classes, I have to pass them into this ApolloClient function (also a library class):要使用这些生成的类中的任何一个,我必须将它们传递给这个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)
      }
    )
  }

I can't figure out how to deal with ConcreteMutation in a generic way.我无法弄清楚如何以通用方式处理ConcreteMutation I want to be able to write a factory function like so:我希望能够像这样编写工厂函数:

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
            ...
        }
    }
}

The fact that this func is in an enum is irrelevant to me: that same code could be in a struct/class/whatever.这个 func 在枚举中的事实与我无关:相同的代码可以在结构/类/任何东西中。 What matters is the function signature.重要的是函数签名。 I want a factory method that returns a GraphQLMutation that can be passed into ApolloClient.perform()我想要一个返回GraphQLMutation的工厂方法,该方法可以传递给ApolloClient.perform()

Because I can't figure out a way to do either of those things, I end up writing a bunch of functions like this instead:因为我无法找到一种方法来做这些事情,所以我最终写了一堆这样的函数:

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)
}

...

That's a lot of duplicated code.这是很多重复的代码。

Depending on how I fiddle with my getMutation signature -- eg, <T: GraphQLMutation>() -> T?取决于我如何处理我的getMutation签名——例如, <T: GraphQLMutation>() -> T? etc. -- I can get the func to compile, but I get a different compile error when I try to pass it into ApolloClient.perform() .等等——我可以让 func 进行编译,但是当我尝试将它传递给ApolloClient.perform()时,我得到了一个不同的编译错误。 Something saying "protocol can only be used as a generic constraint because it has Self or associated type requirements."有人说“协议只能用作通用约束,因为它具有 Self 或相关的类型要求。”

I've researched this a lot, and my research found this article , but I don't think it's an option if the concrete classes implementing the associated type are final?我对此进行了大量研究,我的研究发现了这篇文章,但是如果实现关联类型的具体类是最终的,我认为这不是一个选择?

It's really difficult to figure out if it's possible to use polymorphism in this situation.真的很难弄清楚在这种情况下是否可以使用多态。 I can find plenty of articles of what you can do, but no articles on what you can't do.我可以找到大量的你可以做什么文章,但你不能做任何文章。 My question is:我的问题是:

How do I write getMutation so it returns a value that can be passed into ApolloClient.perform() ?我如何编写getMutation以便它返回一个可以传递给ApolloClient.perform()

The fundamental problem you are running into is that this function signature:您遇到的基本问题是此函数签名:

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

is ambiguous.是模棱两可的。 The reason it's ambiguous is because GraphQLMutation has an associated type ( Data ) and that information doesn't appear anywhere in your function declaration.它不明确的原因是因为 GraphQLMutation 具有关联类型( Data )并且该信息不会出现在您的函数声明中的任何位置。

When you do this:当你这样做时:

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
            ...
        }
    }
}

Each of those branches could have a different type.每个分支都可以有不同的类型。 ConcreteMutation1 could have a Data that is Dormouse while ConcreteMutation3 might have a data value that's an IceCreamTruck . ConcreteMutation1可能有一个Dormouse Data ,而ConcreteMutation3可能有一个IceCreamTruck数据值。 You may be able too tell the compiler to ignore that but then you run into problems later because Dormouse and IceCreamTruck are two structs with VERY different sizes and the compiler might need to use different strategies to pass them as parameters.您也可以告诉编译器忽略它,但稍后会遇到问题,因为DormouseIceCreamTruck是两个大小非常不同的结构,编译器可能需要使用不同的策略将它们作为参数传递。

Apollo.perform is also a template. Apollo.perform也是一个模板。 The compiler is going to write a different function based on that template for each type of mutation you call it with.编译器将根据该模板为您调用它的每种类型的变异编写不同的函数。 In order to do that must know the full type signature of the mutation including what its Data associated type is.为了做到这一点,必须知道突变的完整类型签名,包括它的Data关联类型是什么。 Should the callback be able to handle something the size of a Dormouse , or does it need to be able to handle something the size of an IceCreamTruck ?回调应该能够处理Dormouse大小的东西,还是需要能够处理IceCreamTruck大小的东西?

If the compiler doesn't know, it can't set up the proper calling sequence for the responseHandler .如果编译器不知道,它就无法为responseHandler设置正确的调用序列。 Bad things would happen if you tried to squeeze something the size of an IceCreamTruck through a callback calling sequence that was designed for a parameter the size of a Dormouse !如果你试图通过一个回调调用序列来挤压一个IceCreamTruck大小的东西,那么糟糕的事情就会发生,这个回调调用序列是为一个Dormouse大小的参数设计的!

If the compiler doesn't know what type of Data the mutation has to offer, it can't write a correct version of perform from the template.如果编译器不知道突变必须提供什么类型的Data ,它就无法从模板中编写正确版本的perform

If you've only handed it the result of func getMutation<T: GraphQLMutation>() -> T where you've eliminated evidence of what the Data type is, it doesn't know what version of perform it should write.如果您只将func getMutation<T: GraphQLMutation>() -> T的结果交给它,您已经消除了Data类型是什么的证据,那么它不知道应该编写什么版本的perform

You are trying to hide the type of Data , but you also want the compiler to create a perform function where the type of Data is known.您试图隐藏Data的类型,但您还希望编译器创建一个perform函数,其中Data的类型是已知的。 You can't do both.你不能两者都做。

Maybe you need to implement AnyGraphQLMutation type erased over the associatedtype .也许您需要实现在associatedtype类型上擦除的AnyGraphQLMutation类型。 There are a bunch of resources online for that matter (type erasure), I've found this one pretty exhaustive.网上有很多关于这个问题的资源(类型擦除),我发现这个非常详尽。

I hope this helps in someway:我希望这在某种程度上有所帮助:

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
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM