你可以在Swift中指定一个返回类型作为特定类型的任何集合吗?

gnt*_*skn 6 generics protocols swift

我已经实现了一个自定义队列对象,我想用它来存储控制器中的先前项目.我想在我的控制器中将此类型用作私有变量,并且仅将其作为简单的CollectionType兼容对象公开给外部,以便客户端可以迭代或索引对象,而无需知道任何特定于类的详细信息,例如clear()函数.

Swift协议不能是通用的,所以不幸的是我不能简单地定义一个getter来返回一个CollectionOf<Type>.我已经使用以下抽象基类实现了这种行为,并从中继承了我的集合,但我希望可能有更多的Swift-y和内置方法来实现这一点,希望也不需要子类化:

class AnyCollectionOf<MemberT, IndexT: ForwardIndexType>: CollectionType {
    // Sequence Type
    func generate() -> GeneratorOf<MemberT> {
        fatalError("must override")
    }

    // Collection Type
    typealias Index = IndexT
    typealias Element = MemberT

    subscript (index: Index) -> Element {
        get {
            fatalError("must override")
        }
    }

    var startIndex: Index {
        get {
            fatalError("must override")
        }
    }

    var endIndex: Index {
        get {
            fatalError("must override")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

GSn*_*der 2

不幸的是,您的标题问题的答案是否定的:没有办法将 CollectionType 临时设置为可用作变量或返回类型的独立类型。

像 SequenceType 和 CollectionType 这样的协议要求实现它们的类提供类型别名来填充实现细节,例如元素类型,正如您上面所做的那样。一旦协议添加了这些要求,它就再也不能用作独立类型。您只能根据符合接口的特定类来声明接口。(如果您尝试解决此问题,您可能记得看到过有关“关联类型要求”的不太有用的编译器错误。)

这就是你写不出来的根本原因

func countItems(collection: CollectionType) -> Int { ... }
Run Code Online (Sandbox Code Playgroud)

但必须改为写

func countItems<T: CollectionType>(collection: T) -> Int { ... }
Run Code Online (Sandbox Code Playgroud)

后一种形式确保编译器可以访问实现 CollectionType 协议的对象的实际类型 (T)。

然而,如果您从封装而不是继承的角度思考,那么您想要做的事情可能仍然有一个更清晰的实现。您可以使用一个简单的包装器来阻止对除核心 CollectionType 方法之外的所有内容的访问:

struct ShieldedCollection<UCT: CollectionType> : CollectionType
{
    private var underlying: UCT

    func generate() -> UCT.Generator { return underlying.generate() }
    subscript(index: UCT.Index) -> UCT.Generator.Element { return underlying[index] }
    var startIndex: UCT.Index { return underlying.startIndex }
    var endIndex: UCT.Index { return underlying.endIndex }
}

var foo = [1, 2, 3]
var shieldedFoo = ShieldedCollection(underlying: foo)
Run Code Online (Sandbox Code Playgroud)

(此处,UCT =“底层集合类型”。)

ShieldedCollection 仍然具有作为 CollectionType 的所有常见类型别名,但由于这些别名可以从上下文中推断出来,因此您不必显式指定它们。

这种通用方法的缺陷(不幸的是,这是一个相当大的缺陷)是底层类型仍然泄漏到 API 中。上例中shieldedFoo的类型是

ShieldedCollection<Array<Int>>
Run Code Online (Sandbox Code Playgroud)

由于您的底层集合是自定义对象,因此即使客户端无法直接访问类本身,其名称仍然可能在 API 中泄漏。但请注意,这不是功能问题,因为不可能通过 ShieldedCollection 包装器访问底层对象。此外,消费者永远不必自己编写类型 - 他们只需使用 previousItems() 的结果作为 CollectionType,编译器就会解开所有内容。

如果您确实想隐藏所有对底层集合类型的提及,则可以通过将 UCT 定义移动到 ShieldedCollection 内来编写上面包装器的特定于任务的模拟:

struct ShieldedCollection<T> : CollectionType    // Changed!
{
    typealias UCT = [T]                          // Added!

    private var underlying: UCT                  // Everything else identical

    func generate() -> UCT.Generator { return underlying.generate() }
    subscript(index: UCT.Index) -> UCT.Generator.Element { return underlying[index] }
    var startIndex: UCT.Index { return underlying.startIndex }
    var endIndex: UCT.Index { return underlying.endIndex }
}

var foo = [1, 2, 3]
var shieldedFoo = ShieldedCollection(underlying: foo)
Run Code Online (Sandbox Code Playgroud)

在这里,您通过放弃完全的通用性来使返回类型整洁——此版本的 ShieldedCollection 仅适用于作为数组的底层 CollectionType。(当然,您只需将类型别名中的 UCT 替换为您自己的自定义集合类型即可。)