Swift: 是否可以为协议添加协议扩展?

Swift: Is it possible to add a protocol extension to a protocol?

假设我有两个协议:

protocol TheirPcol {}
protocol MyPcol {
    func extraFunc()
}

我想做的是为 'TheirPcol' 创建一个协议扩展,让 extraFunc() 可以处理任何符合 'TheirPcol' 的东西。所以像这样:

extension TheirPcol : MyPcol { // Error 'Extension of protocol 'TheirPcol' cannot have an inheritance clause.
    func extraFunc() { /* do magic */}
}

struct TheirStruct:TheirPcol {}
let inst = TheirStruct()
inst.extraFunc()

关键在于 'TheirPcol'、'TheirStruct' 都由我无法控制的外部 API 处理。所以我通过了实例 'inst'.

这能做到吗?或者我将不得不做这样的事情:

struct TheirStruct:TheirPcol {}
let inst = TheirStruct() as! MyPcol
inst.extraFunc()

似乎有两个 use-cases 您可能想要做您正在做的事情的原因。在第一个use-case中,Swift可以让你为所欲为,但在第二个use-case中就不是很干净了。我猜你属于第二类,但我会通过两者。

扩展 TheirPcol

的功能

您可能想要这样做的一个原因是简单地为 TheirPcol 提供额外的功能。就像编译器错误所说的那样,您不能扩展 Swift 协议以符合其他协议。但是,您可以简单地扩展 TheirPcol.

extension TheirPcol {
    func extraFunc() { /* do magic */ }
}

在这里,您为所有符合 TheirPcol 的对象提供方法 extraFunc() 并为其提供默认实现。这完成了为符合 TheirPcol 的对象扩展功能的任务,如果您希望它也适用于您自己的对象,那么您可以使您的对象符合 TheirPcol。然而,在许多情况下,您希望将 MyPcol 作为您的主要协议,而只将 TheirPcol 视为符合 MyPcol。不幸的是,Swift 目前不支持声明与其他协议一致的协议扩展。

MyPcol

一样使用 TheirPcol 个对象

在您确实需要单独存在 MyPcol 的用例(很可能是您的用例)中,据我所知,还没有干净的方法来做您想要的事情。这里有一些有效但 non-ideal 的解决方案:

环绕 TheirPcol

一种可能比较麻烦的方法是使用 structclass,如下所示:

struct TheirPcolWrapper<T: TheirPcol>: MyPcol {
    var object: T

    func extraFunc() { /* Do magic using object */ }
}

理论上,当您需要使现有对象实例符合 MyPcol 时,您可以使用此结构作为转换的替代方法,如您的示例所示。或者,如果您有接受 MyPcol 作为通用参数的函数,您可以创建接受 TheirPcol 的等价函数,然后将其转换为 TheirPcolWrapper 并将其发送给接受其他函数在 MyPcol.

另一件需要注意的事情是,如果你被传递给一个 TheirPcol 的对象,那么你将无法创建一个 TheirPcolWrapper 实例,除非先将其转换为显式类型。这是由于 Swift 的一些泛型限制。所以,像这样的对象可以作为替代:

struct TheirPcolWrapper: MyPcol {
    var object: MyPcol

    func extraFunc() { /* Do magic using object */ }
}

这意味着您可以创建一个 TheirPcolWrapper 实例,而无需知道给定的 TheirPcol 的显式类型。

但是,对于一个大型项目,这两者都可能很快变得混乱。

使用子协议扩展单个对象

还有一个 non-ideal 解决方案是扩展您知道符合 TheirPcol 并且您知道您希望支持的每个对象。例如,假设您知道 ObjectAObjectB 符合 TheirPcol。您可以创建 MyPcol 的子协议,然后显式声明两个对象的一致性,如下所示:

protocol BridgedToMyPcol: TheirPcol, MyPcol {}

extension BridgedToMyPcol {
    func extraFunc() {
        // Do magic here, given that the object is guaranteed to conform to TheirPcol
    }
}

extension ObjectA: BridgedToMyPcol {}
extension ObjectB: BridgedToMyPcol {}

遗憾的是,如果您希望支持大量对象,或者如果您无法提前知道这些对象是什么,这种方法就会失败。当您不知道给定的 TheirPcol 的显式类型时,它也会成为一个问题,尽管您可以使用 type(of:) 来获取元类型。

关于 Swift 4

的注释

您应该查看 Conditional conformances,一项接受包含在 Swift 4 中的提案。具体来说,该提案概述了具有以下扩展的能力:

extension Array: Equatable where Element: Equatable {
    static func ==(lhs: Array<Element>, rhs: Array<Element>) -> Bool { ... }
}

虽然这不是您要问的,但在底部您会发现 "Alternatives considered",其中有一个名为 "Extending protocols to conform to protocols" 的 sub-section,这更符合您的要求正在尝试做。它提供了以下示例:

extension Collection: Equatable where Iterator.Element: Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        // ...
    }
}

然后陈述如下:

This protocol extension would make any Collection of Equatable elements Equatable, which is a powerful feature that could be put to good use. Introducing conditional conformances for protocol extensions would exacerbate the problem of overlapping conformances, because it would be unreasonable to say that the existence of the above protocol extension means that no type that conforms to Collection could declare its own conformance to Equatable, conditional or otherwise.

虽然我意识到您并不是在要求具有条件一致性的能力,但这是我能找到的关于扩展协议以符合其他协议的讨论的最接近的东西.