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

时间:2014-11-27 01:21:46

标签: generics swift protocols

我已经实现了一个自定义队列对象,我想用它来存储控制器中的先前项目。我想在我的控制器中将此类型用作私有变量,并且仅将其作为简单的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")
        }
    }
}

3 个答案:

答案 0 :(得分:2)

遗憾的是,您的标题问题的答案是否定的:没有办法将CollectionType作为可以用作变量或返回类型的独立类型进行陪审。

SequenceType和CollectionType等协议要求实现它们的类提供typealiases以填充实现细节,例如元素类型,如上所述。一旦协议添加了这些要求,它就再也不能用作独立类型。您只能根据符合它的特定类声明接口。 (如果您已尝试解决此问题,您可能会记得看到关于&#34;相关类型要求的#13;

的非常有用的编译器错误。

这是你不能写

的基本原因
func countItems(collection: CollectionType) -> Int { ... }

但必须改为写

func countItems<T: CollectionType>(collection: T) -> Int { ... }

后一种形式确保编译器可以访问实现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)

(此处,UCT =&#34;基础集合类型&#34;。)

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

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

ShieldedCollection<Array<Int>>

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

如果您真的想要隐藏所有提及的底层集合类型,您可以通过在ShieldedCollection中移动UCT定义来编写上面包装器的特定于任务的模拟:

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)

这里通过放弃完全的通用性来使返回类型整洁 - 这个版本的ShieldedCollection仅适用于作为Arrays的底层CollectionTypes。 (当然,您只需在UCT的类型中替换您自己的自定义集合类型。)

答案 1 :(得分:0)

我有一个带有2个标签和1个图像视图的堆栈视图,并成功使用它:

open var labels: [UILabel] {
        return views.filter({ $0 is UILabel }).flatMap({ $0 }) as! [UILabel]
    }

如果你想让它变得通用:filterflatMap(来自Swift 4的compactMap)和强制可选

答案 2 :(得分:0)

从Swift 5.1开始,最终可以通过some关键字来实现:

func upNext() -> some Collection { ... }