如何解决Swift中带有关联类型的协议方法返回类型错误?

8dtrkrch  于 2023-06-21  发布在  Swift
关注(0)|答案(1)|浏览(138)

我有一个protocol Request,其关联类型为Response,还有一个示例方法getToken,它返回关联类型为TokenError的Publisher。
但是,我得到一个编译错误“示例方法'getToken'的返回类型要求'RequestType.Response'符合any Token“。
如何修复此错误并确保RequestType.Response符合any Token?任何建议或见解将不胜感激。

func getToken<RequestType: Request>(_ request: RequestType) -> some Publisher<any Token, any Error> where RequestType.Response: Token {
  tokenClient.send(request)
}

// error:Return type of instance method 'getToken' requires that 'RequestType.Response' conform to 'any Token'

如果我使用some Publisher<TokenWrapper, any Error>,它工作得很好,因为在我的应用程序中有几种类型的具体令牌。我不想重复.map(TokenWrapper.init)这一行。

struct TokenWrapper {
  let wrappedValue: any Token
}

// send<RequestType: Request>(_ request: RequestType, encoder: JSONEncoder? = nil, decoder: JSONDecoder = .init()) -> AnyPublisher<RequestType.Response, Error>

// current code works fine
func getToken<RequestType: Request>(_ request: RequestType) -> some Publisher<TokenWrapper, any Error> {
  tokenClient.send(request)
    .map(TokenWrapper.init)
}
func handle(_ pubilsher: some Publisher<TokenWrapper, any Error>) {
  publihsher
    ...
    ...
    .store(in: subscriptions)
}

// what I want the code looks like
func getToken<RequestType: Request>(_ request: RequestType) -> some Publisher<any Token, any Error> where RequestType.Response: Token {
  tokenClient.send(request)
}
func handle(_ pubilsher: some Publisher<any Token, any Error>) {
  publihsher
    .map(TokenWrapper.init)
    ...
    ...
    .store(in: subscriptions)
}

既然where子句do constraints RequestType.Response符合Token,为什么Xcode仍然抱怨错误?

ubof19bj

ubof19bj1#

您可能会认为any Token在这里的意思是“任何符合Token的类型”,但这不是它的意思。它的意思是“一个符合Token的类型的存在 Package 器”。

some Publisher<any Token, any Error>

要求发布的类型是字面上的any Token(存在性 Package 器)。而不是“某种符合Token的具体类型”。此外,any Token本身也不符合Token,因为存在式不符合它们的协议。(您可能会遇到any Error确实符合Error的事实,但这是编译器仅为Error处理的特殊情况异常。它不适用于任何其他协议。)
要做到这一点,您需要一个显式的类型擦除器,这正是您用TokenWrapper创建的。通常情况下,这将被命名为AnyToken,它将符合Token,但这仍然是基本的方法。
要与Publisher的工作方式相匹配,您可能需要重命名此.eraseToAnyToken

extension Publisher where Output: Token {
    func eraseToAnyToken() -> some Publisher<AnyToken, Failure> {
        self.map(AnyToken.init(token:))
    }
}

extension Publisher where Output == any Token {
    func eraseToAnyToken() -> some Publisher<AnyToken, Failure> {
        self.map(AnyToken.init(token:))
    }
}

然后你可能有:

func getToken<RequestType: Request>(_ request: RequestType) -> some Publisher<AnyToken, any Error> {
    tokenClient.send(request)
        .eraseToAnyToken()
}

但你不能摆脱类型橡皮擦与这种设计。
从这个问题分开,但一些建议:
我强烈建议重新考虑将Token作为协议。真的是这样吗或者Token可以是一个具体的类型,有多种方法来初始化它?这样做可以大大简化这一切。创建太多的互连协议和泛型是一个典型的错误,网络堆栈是一个非常常见的地方,人们通过使系统的错误部分通用来犯这个错误。
我强烈建议使用async/await而不是合并。在使用新系统时,关于合并的许多棘手的事情变得简单得多,苹果已经从Combine大幅后退。

相关问题