通过指定类型将“具有关联类型的协议”转换为常规协议

Posted

技术标签:

【中文标题】通过指定类型将“具有关联类型的协议”转换为常规协议【英文标题】:Turn a "protocol with associated type" in to a regular protocol by specifying the type 【发布时间】:2018-12-19 11:55:56 【问题描述】:

我有以下协议:

protocol PieceViewGateway 
    subscript(_ identifier: PieceIdentifier) -> UIView get

我在很多地方都以这样的方式使用它:

struct SomeKindOfThing 
  let viewGateway: PieceViewGateway

这一切都很好,非常好。

这是协议的具体实现的一个示例(还有其他实现):

struct ViewDictionaryPieceViewGateway: PieceViewGateway 
    let viewDictionary: [PieceIdentifier: UIView]

    subscript(identifier: PieceIdentifier) -> UIView 
        guard let item = viewDictionary[identifier] else  
          fatalError("Gateway has no value for key: \(identifier)") 
        

        return item
    

我有几个这样的协议。另一个是PieceValueGateway,它返回Int,而不是UIView

我不想为我需要网关的各种不同“方面”实现类似ViewDictionaryPieceViewGateway 的东西。

我试图通过定义一个具有关联类型的协议来模拟网关来实现这一点:

protocol PieceAspectGateway 
    associatedtype Aspect
    subscript(_ identifier: PieceIdentifier) -> Aspect get


然后我将PieceViewGateway 与此一致:

protocol PieceViewGateway: PieceAspectGateway 
    subscript(_ identifier: PieceIdentifier) -> UIView get

但是,这会产生很多编译错误:

Protocol 'PieceViewGateway' 只能用作通用约束,因为它具有 Self 或关联的类型要求

在我添加一致性PieceViewGateway: PieceAspectGateway 之前,代码很好报告了错误。例如,SomeKindOfThing 声明它有一个let viewGateway: PieceViewGateway

我也尝试过这样的一致性:

protocol PieceViewGateway: PieceAspectGateway where Aspect == UIView 
    subscript(_ identifier: PuzzlePieceIdentifier) -> UIView get

像这样:

protocol PieceViewGateway: PieceAspectGateway 
    typealias Aspect = UIView
    subscript(_ identifier: PuzzlePieceIdentifier) -> UIView get

…但只要将PieceViewGateway 用作协议,所有这些变体都会产生相同的错误。

有什么方法可以实现吗?

谢谢。

【问题讨论】:

你能在问题中添加错误的行吗?但一般现在不能使用varName: PieceViewGateway ,必须声明泛型<T: PieceViewGateway>(varName: T) 【参考方案1】:

您可以使用protocolassociatedtype 以及类型橡皮擦来创建任何类型的Gateway。请看下文,

protocol PieceAspectGateway 
    associatedtype Aspect
    subscript(_ identifier: PieceIdentifier) -> Aspect get



struct AnyGateway<T>: PieceAspectGateway 
    let dictionary: [PieceIdentifier: T]

    subscript(identifier: PieceIdentifier) -> T 
        guard let item = dictionary[identifier] else 
            fatalError("Gateway has no value for key: \(identifier)")
        
        return item
    

用法

let viewGateway: AnyGateway<UIView>
let viewGateways: [AnyGateway<UIView>] = []

let intGateway: AnyGateway<Int>
let intGateways: [AnyGateway<Int>] = []

let stringGateway: AnyGateway<String>
let stringGateways: [AnyGateway<String>] = []

【讨论】:

嗨——谢谢。我通常有点理解为什么需要擦除类型。在这种情况下,我不得不说我很困惑。不过还是谢谢你的回答!【参考方案2】:

你想要完成的事情很简单:

protocol PieceAspectGateway 
    associatedtype Aspect
    subscript(_ identifier: PieceIdentifier) -> Aspect get

所以你有这个协议。当你想继承它并使用更具体的类型时,你可以使用泛型约束:

protocol IntPieceAspectGateway: PieceAspectGateway where Aspect == Int 
    ...

【讨论】:

当您尝试像let a: [IntPieceAspectGateway] = [] 一样声明array 时,这将再次导致相同的error 当然会。如果要使用泛型集合,则必须使用具体类型。在 Swift 中,无法使用具有关联类型的协议作为泛型参数。所以你必须创建一个符合它的结构或类 是的,但这是 OP 已经拥有的。他想找到一种不创建n 数量的具体类型的方法。 我向你保证,它不会。这是我尝试的第二件事(在尝试PieceViewGateway 中的typealias Aspect = UIView 之后。它不会使PieceViewGateway 成为您可以传递的“正常”协议。它仍然是一个类型约束,作为具有关联类型的协议PieceAspectGateway 是。 @Benjohn 当我说会的时候,我正在回答卡姆兰。是的,我知道它会在不创建具体类型的情况下给您同样的错误。在卡姆兰的回答中,我认为不需要该协议。这样你就可以创建一个带有泛型参数的基类,协议是没用的,因为你可以在基类中声明这些方法。

以上是关于通过指定类型将“具有关联类型的协议”转换为常规协议的主要内容,如果未能解决你的问题,请参考以下文章

阵列中具有关联类型的协议 - 替代解决方案

typescript 将IFile从离子转换为常规文件类型

如何将存储为 VARCHAR 的 UTC 日期时间值转换为常规日期时间

将符合多个协议的类型转换为单个协议

Swift 同时将对象转换为类型和协议

无法将协议的通用关联类型的值转换为预期的参数类型